mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 11:18:41 +00:00
improve image editor
This commit is contained in:
parent
9a162b5b2f
commit
4667ec09cc
21 changed files with 297 additions and 2022 deletions
|
|
@ -1 +0,0 @@
|
||||||
The image editor is based on: https://github.com/hsbijarniya/image_editor_plus/tree/main
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
enum EditorMode {
|
|
||||||
brush,
|
|
||||||
filter,
|
|
||||||
}
|
|
||||||
|
|
@ -34,21 +34,4 @@ class ImageItem {
|
||||||
return loader.complete(false);
|
return loader.complete(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ImageItem fromJson(Map json) {
|
|
||||||
var image = ImageItem(json['bytes']);
|
|
||||||
|
|
||||||
image.width = json['width'];
|
|
||||||
image.height = json['height'];
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map toJson() {
|
|
||||||
return {
|
|
||||||
'height': height,
|
|
||||||
'width': width,
|
|
||||||
'bytes': bytes,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,41 +12,6 @@ class Layer {
|
||||||
this.rotation = 0,
|
this.rotation = 0,
|
||||||
this.scale = 1,
|
this.scale = 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
copyFrom(Map json) {
|
|
||||||
offset = Offset(json['offset'][0], json['offset'][1]);
|
|
||||||
opacity = json['opacity'];
|
|
||||||
rotation = json['rotation'];
|
|
||||||
scale = json['scale'];
|
|
||||||
}
|
|
||||||
|
|
||||||
static Layer fromJson(Map json) {
|
|
||||||
switch (json['type']) {
|
|
||||||
case 'BackgroundLayer':
|
|
||||||
return BackgroundLayerData.fromJson(json);
|
|
||||||
case 'EmojiLayer':
|
|
||||||
return EmojiLayerData.fromJson(json);
|
|
||||||
case 'ImageLayer':
|
|
||||||
return ImageLayerData.fromJson(json);
|
|
||||||
case 'LinkLayer':
|
|
||||||
return LinkLayerData.fromJson(json);
|
|
||||||
case 'TextLayer':
|
|
||||||
return TextLayerData.fromJson(json);
|
|
||||||
case 'BackgroundBlurLayer':
|
|
||||||
return BackgroundBlurLayerData.fromJson(json);
|
|
||||||
default:
|
|
||||||
return Layer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map toJson() {
|
|
||||||
return {
|
|
||||||
'offset': [offset.dx, offset.dy],
|
|
||||||
'opacity': opacity,
|
|
||||||
'rotation': rotation,
|
|
||||||
'scale': scale,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [BackgroundLayer]
|
/// Attributes used by [BackgroundLayer]
|
||||||
|
|
@ -56,20 +21,6 @@ class BackgroundLayerData extends Layer {
|
||||||
BackgroundLayerData({
|
BackgroundLayerData({
|
||||||
required this.image,
|
required this.image,
|
||||||
});
|
});
|
||||||
|
|
||||||
static BackgroundLayerData fromJson(Map json) {
|
|
||||||
return BackgroundLayerData(
|
|
||||||
image: ImageItem.fromJson(json['image']),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map toJson() {
|
|
||||||
return {
|
|
||||||
'type': 'BackgroundLayer',
|
|
||||||
'image': image.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [EmojiLayer]
|
/// Attributes used by [EmojiLayer]
|
||||||
|
|
@ -85,26 +36,6 @@ class EmojiLayerData extends Layer {
|
||||||
super.rotation,
|
super.rotation,
|
||||||
super.scale,
|
super.scale,
|
||||||
});
|
});
|
||||||
|
|
||||||
static EmojiLayerData fromJson(Map json) {
|
|
||||||
var layer = EmojiLayerData(
|
|
||||||
text: json['text'],
|
|
||||||
size: json['size'],
|
|
||||||
);
|
|
||||||
|
|
||||||
layer.copyFrom(json);
|
|
||||||
return layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map toJson() {
|
|
||||||
return {
|
|
||||||
'type': 'EmojiLayer',
|
|
||||||
'text': text,
|
|
||||||
'size': size,
|
|
||||||
...super.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [ImageLayer]
|
/// Attributes used by [ImageLayer]
|
||||||
|
|
@ -120,76 +51,19 @@ class ImageLayerData extends Layer {
|
||||||
super.rotation,
|
super.rotation,
|
||||||
super.scale,
|
super.scale,
|
||||||
});
|
});
|
||||||
|
|
||||||
static ImageLayerData fromJson(Map json) {
|
|
||||||
var layer = ImageLayerData(
|
|
||||||
image: ImageItem.fromJson(json['image']),
|
|
||||||
size: json['size'],
|
|
||||||
);
|
|
||||||
|
|
||||||
layer.copyFrom(json);
|
|
||||||
return layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map toJson() {
|
|
||||||
return {
|
|
||||||
'type': 'ImageLayer',
|
|
||||||
'image': image.toJson(),
|
|
||||||
'size': size,
|
|
||||||
...super.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [TextLayer]
|
/// Attributes used by [TextLayer]
|
||||||
class TextLayerData extends Layer {
|
class TextLayerData extends Layer {
|
||||||
String text;
|
String text;
|
||||||
double size;
|
|
||||||
Color color, background;
|
|
||||||
double backgroundOpacity;
|
|
||||||
TextAlign align;
|
|
||||||
|
|
||||||
TextLayerData({
|
TextLayerData({
|
||||||
required this.text,
|
required this.text,
|
||||||
this.size = 64,
|
|
||||||
this.color = Colors.white,
|
|
||||||
this.background = Colors.transparent,
|
|
||||||
this.backgroundOpacity = 0,
|
|
||||||
this.align = TextAlign.left,
|
|
||||||
super.offset,
|
super.offset,
|
||||||
super.opacity,
|
super.opacity,
|
||||||
super.rotation,
|
super.rotation,
|
||||||
super.scale,
|
super.scale,
|
||||||
});
|
});
|
||||||
|
|
||||||
static TextLayerData fromJson(Map json) {
|
|
||||||
var layer = TextLayerData(
|
|
||||||
text: json['text'],
|
|
||||||
size: json['size'],
|
|
||||||
color: Color(json['color']),
|
|
||||||
background: Color(json['background']),
|
|
||||||
backgroundOpacity: json['backgroundOpacity'],
|
|
||||||
align: TextAlign.values.firstWhere((e) => e.name == json['align']),
|
|
||||||
);
|
|
||||||
|
|
||||||
layer.copyFrom(json);
|
|
||||||
return layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map toJson() {
|
|
||||||
return {
|
|
||||||
'type': 'TextLayer',
|
|
||||||
'text': text,
|
|
||||||
'size': size,
|
|
||||||
'color': color.value,
|
|
||||||
'background': background.value,
|
|
||||||
'backgroundOpacity': backgroundOpacity,
|
|
||||||
'align': align.name,
|
|
||||||
...super.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [TextLayer]
|
/// Attributes used by [TextLayer]
|
||||||
|
|
@ -212,67 +86,4 @@ class LinkLayerData extends Layer {
|
||||||
super.rotation,
|
super.rotation,
|
||||||
super.scale,
|
super.scale,
|
||||||
});
|
});
|
||||||
|
|
||||||
static LinkLayerData fromJson(Map json) {
|
|
||||||
var layer = LinkLayerData(
|
|
||||||
text: json['text'],
|
|
||||||
size: json['size'],
|
|
||||||
color: Color(json['color']),
|
|
||||||
background: Color(json['background']),
|
|
||||||
backgroundOpacity: json['backgroundOpacity'],
|
|
||||||
align: TextAlign.values.firstWhere((e) => e.name == json['align']),
|
|
||||||
);
|
|
||||||
|
|
||||||
layer.copyFrom(json);
|
|
||||||
return layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map toJson() {
|
|
||||||
return {
|
|
||||||
'type': 'LinkLayer',
|
|
||||||
'text': text,
|
|
||||||
'size': size,
|
|
||||||
'color': color.value,
|
|
||||||
'background': background.value,
|
|
||||||
'backgroundOpacity': backgroundOpacity,
|
|
||||||
'align': align.name,
|
|
||||||
...super.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attributes used by [BackgroundBlurLayer]
|
|
||||||
class BackgroundBlurLayerData extends Layer {
|
|
||||||
Color color;
|
|
||||||
double radius;
|
|
||||||
|
|
||||||
BackgroundBlurLayerData({
|
|
||||||
required this.color,
|
|
||||||
required this.radius,
|
|
||||||
super.offset,
|
|
||||||
super.opacity,
|
|
||||||
super.rotation,
|
|
||||||
super.scale,
|
|
||||||
});
|
|
||||||
|
|
||||||
static BackgroundBlurLayerData fromJson(Map json) {
|
|
||||||
var layer = BackgroundBlurLayerData(
|
|
||||||
color: Color(json['color']),
|
|
||||||
radius: json['radius'],
|
|
||||||
);
|
|
||||||
|
|
||||||
layer.copyFrom(json);
|
|
||||||
return layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map toJson() {
|
|
||||||
return {
|
|
||||||
'type': 'BackgroundBlurLayer',
|
|
||||||
'color': color.value,
|
|
||||||
'radius': radius,
|
|
||||||
...super.toJson(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math' as math;
|
|
||||||
import 'package:flex_color_picker/flex_color_picker.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
@ -8,75 +6,29 @@ 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/image_item.dart';
|
||||||
import 'package:twonly/src/components/image_editor/data/layer.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/layers_viewer.dart';
|
||||||
import 'package:twonly/src/components/image_editor/loading_screen.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/modules/all_emojis.dart';
|
import 'package:twonly/src/components/image_editor/modules/all_emojis.dart';
|
||||||
import 'package:twonly/src/components/image_editor/modules/text.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/options.dart' as o;
|
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
|
|
||||||
late Size viewportSize;
|
List<Layer> layers = [];
|
||||||
double viewportRatio = 1;
|
List<Layer> undoLayers = [];
|
||||||
|
List<Layer> removedLayers = [];
|
||||||
|
|
||||||
List<Layer> layers = [], undoLayers = [], removedLayers = [];
|
|
||||||
Map<String, String> _translations = {};
|
|
||||||
|
|
||||||
String i18n(String sourceString) =>
|
|
||||||
_translations[sourceString.toLowerCase()] ?? sourceString;
|
|
||||||
|
|
||||||
/// Image editor with all option available
|
|
||||||
class ImageEditor extends StatefulWidget {
|
class ImageEditor extends StatefulWidget {
|
||||||
final dynamic image;
|
final dynamic image;
|
||||||
final String? savePath;
|
final String? savePath;
|
||||||
final o.BrushOption? brushOption;
|
|
||||||
final o.EmojiOption? emojiOption;
|
|
||||||
final o.TextOption? textOption;
|
|
||||||
|
|
||||||
const ImageEditor({
|
const ImageEditor({
|
||||||
super.key,
|
super.key,
|
||||||
this.image,
|
this.image,
|
||||||
this.savePath,
|
this.savePath,
|
||||||
this.brushOption = const o.BrushOption(),
|
|
||||||
this.emojiOption = const o.EmojiOption(),
|
|
||||||
this.textOption = const o.TextOption(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
createState() => _ImageEditorState();
|
createState() => _ImageEditorState();
|
||||||
|
|
||||||
static setI18n(Map<String, String> translations) {
|
|
||||||
translations.forEach((key, value) {
|
|
||||||
_translations[key.toLowerCase()] = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set custom theme properties default is dark theme with white text
|
|
||||||
static ThemeData theme = ThemeData(
|
|
||||||
scaffoldBackgroundColor: Colors.black,
|
|
||||||
colorScheme: const ColorScheme.dark(
|
|
||||||
surface: Colors.black,
|
|
||||||
),
|
|
||||||
appBarTheme: const AppBarTheme(
|
|
||||||
backgroundColor: Colors.black87,
|
|
||||||
iconTheme: IconThemeData(color: Colors.white),
|
|
||||||
systemOverlayStyle: SystemUiOverlayStyle.light,
|
|
||||||
toolbarTextStyle: TextStyle(color: Colors.white),
|
|
||||||
titleTextStyle: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
),
|
|
||||||
iconTheme: const IconThemeData(
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
textTheme: const TextTheme(
|
|
||||||
bodyMedium: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ImageEditorState extends State<ImageEditor> {
|
class _ImageEditorState extends State<ImageEditor> {
|
||||||
ImageItem currentImage = ImageItem();
|
ImageItem currentImage = ImageItem();
|
||||||
|
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -88,65 +40,52 @@ class _ImageEditorState extends State<ImageEditor> {
|
||||||
List<Widget> get filterActions {
|
List<Widget> get filterActions {
|
||||||
return [
|
return [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.close, size: 30),
|
icon: FaIcon(
|
||||||
|
FontAwesomeIcons.xmark,
|
||||||
|
size: 30,
|
||||||
|
shadows: <Shadow>[
|
||||||
|
Shadow(
|
||||||
|
color: const Color.fromARGB(122, 0, 0, 0),
|
||||||
|
blurRadius: 1.0,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(
|
Expanded(child: Container()),
|
||||||
width: MediaQuery.of(context).size.width - 48,
|
IconButton(
|
||||||
child: Row(children: [
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
IconButton(
|
icon: Icon(Icons.undo,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
color: layers.length > 1 || removedLayers.isNotEmpty
|
||||||
icon: Icon(Icons.undo,
|
? Colors.white
|
||||||
color: layers.length > 1 || removedLayers.isNotEmpty
|
: Colors.grey),
|
||||||
? Colors.white
|
onPressed: () {
|
||||||
: Colors.grey),
|
if (removedLayers.isNotEmpty) {
|
||||||
onPressed: () {
|
layers.add(removedLayers.removeLast());
|
||||||
if (removedLayers.isNotEmpty) {
|
setState(() {});
|
||||||
layers.add(removedLayers.removeLast());
|
return;
|
||||||
setState(() {});
|
}
|
||||||
return;
|
if (layers.length <= 1) return; // do not remove image layer
|
||||||
}
|
undoLayers.add(layers.removeLast());
|
||||||
|
setState(() {});
|
||||||
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(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
icon: const Icon(Icons.check),
|
|
||||||
onPressed: () async {
|
|
||||||
resetTransformation();
|
|
||||||
setState(() {});
|
|
||||||
|
|
||||||
var loadingScreen = showLoadingScreen(context);
|
|
||||||
|
|
||||||
Uint8List? editedImageBytes = await getMergedImage();
|
|
||||||
|
|
||||||
loadingScreen.hide();
|
|
||||||
|
|
||||||
if (mounted) Navigator.pop(context, editedImageBytes);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
|
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)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,14 +98,8 @@ class _ImageEditorState extends State<ImageEditor> {
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
double lastScaleFactor = 1, scaleFactor = 1;
|
|
||||||
double widthRatio = 1, heightRatio = 1, pixelRatio = 1;
|
double widthRatio = 1, heightRatio = 1, pixelRatio = 1;
|
||||||
|
|
||||||
resetTransformation() {
|
|
||||||
scaleFactor = 1;
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// obtain image Uint8List by merging layers
|
/// obtain image Uint8List by merging layers
|
||||||
Future<Uint8List?> getMergedImage() async {
|
Future<Uint8List?> getMergedImage() async {
|
||||||
Uint8List? image;
|
Uint8List? image;
|
||||||
|
|
@ -185,68 +118,36 @@ class _ImageEditorState extends State<ImageEditor> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
viewportSize = MediaQuery.of(context).size;
|
|
||||||
pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
return Stack(children: [
|
return Stack(children: [
|
||||||
GestureDetector(
|
SizedBox(
|
||||||
onScaleUpdate: (details) {
|
height: currentImage.height / pixelRatio,
|
||||||
// print(details);
|
width: currentImage.width / pixelRatio,
|
||||||
|
child: Screenshot(
|
||||||
// move
|
controller: screenshotController,
|
||||||
if (details.pointerCount == 1) {
|
child: LayersViewer(
|
||||||
// print(details.focalPointDelta);
|
layers: layers,
|
||||||
// x += details.focalPointDelta.dx;
|
onUpdate: () {
|
||||||
// y += details.focalPointDelta.dy;
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// scale
|
|
||||||
if (details.pointerCount == 2) {
|
|
||||||
// print([details.horizontalScale, details.verticalScale]);
|
|
||||||
if (details.horizontalScale != 1) {
|
|
||||||
scaleFactor = lastScaleFactor *
|
|
||||||
math.min(details.horizontalScale, details.verticalScale);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
},
|
||||||
}
|
editable: true,
|
||||||
},
|
|
||||||
onScaleEnd: (details) {
|
|
||||||
lastScaleFactor = scaleFactor;
|
|
||||||
},
|
|
||||||
child: SizedBox(
|
|
||||||
height: currentImage.height / pixelRatio,
|
|
||||||
width: currentImage.width / pixelRatio,
|
|
||||||
child: Screenshot(
|
|
||||||
controller: screenshotController,
|
|
||||||
child: LayersViewer(
|
|
||||||
layers: layers,
|
|
||||||
onUpdate: () {
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
editable: true,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
top: 0,
|
top: 5,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: Container(
|
child: SafeArea(
|
||||||
decoration: BoxDecoration(
|
child: Row(
|
||||||
color: Colors.black.withAlpha(75),
|
children: filterActions,
|
||||||
),
|
|
||||||
child: SafeArea(
|
|
||||||
child: Row(
|
|
||||||
children: filterActions,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 0,
|
right: 0,
|
||||||
top: 50,
|
top: 100,
|
||||||
child: Container(
|
child: Container(
|
||||||
// color: Colors.black45,
|
// color: Colors.black45,
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
|
|
@ -260,112 +161,77 @@ class _ImageEditorState extends State<ImageEditor> {
|
||||||
// ],
|
// ],
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: Column(
|
||||||
scrollDirection: Axis.horizontal,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Column(
|
children: <Widget>[
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
BottomButton(
|
||||||
children: <Widget>[
|
icon: FontAwesomeIcons.font,
|
||||||
if (widget.textOption != null)
|
onTap: () async {
|
||||||
BottomButton(
|
undoLayers.clear();
|
||||||
icon: Icons.text_fields,
|
removedLayers.clear();
|
||||||
onTap: () async {
|
|
||||||
TextLayerData? layer = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const TextEditorImage(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (layer == null) return;
|
layers.add(
|
||||||
|
TextLayerData(
|
||||||
|
text: "Test",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
undoLayers.clear();
|
setState(() {});
|
||||||
removedLayers.clear();
|
},
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
layers.add(layer);
|
if (drawing != null) {
|
||||||
|
undoLayers.clear();
|
||||||
|
removedLayers.clear();
|
||||||
|
|
||||||
setState(() {});
|
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 (widget.brushOption != null)
|
|
||||||
BottomButton(
|
|
||||||
icon: Icons.edit,
|
|
||||||
onTap: () async {
|
|
||||||
if (widget.brushOption!.translatable) {
|
|
||||||
var drawing = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => ImageEditorDrawing(
|
|
||||||
image: currentImage,
|
|
||||||
options: widget.brushOption!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (drawing != null) {
|
if (layer == null) return;
|
||||||
undoLayers.clear();
|
|
||||||
removedLayers.clear();
|
|
||||||
|
|
||||||
layers.add(
|
undoLayers.clear();
|
||||||
ImageLayerData(
|
removedLayers.clear();
|
||||||
image: ImageItem(drawing),
|
layers.add(layer);
|
||||||
offset: Offset(
|
|
||||||
-currentImage.width / 4,
|
|
||||||
-currentImage.height / 4,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
},
|
||||||
} else {
|
),
|
||||||
resetTransformation();
|
],
|
||||||
var loadingScreen = showLoadingScreen(context);
|
|
||||||
var mergedImage = await getMergedImage();
|
|
||||||
loadingScreen.hide();
|
|
||||||
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
var drawing = await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => ImageEditorDrawing(
|
|
||||||
image: ImageItem(mergedImage!),
|
|
||||||
options: widget.brushOption!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (drawing != null) {
|
|
||||||
currentImage.load(drawing);
|
|
||||||
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (widget.emojiOption != null)
|
|
||||||
BottomButton(
|
|
||||||
icon: FontAwesomeIcons.faceSmile,
|
|
||||||
onTap: () async {
|
|
||||||
EmojiLayerData? layer = await showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return const Emojies();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (layer == null) return;
|
|
||||||
|
|
||||||
undoLayers.clear();
|
|
||||||
removedLayers.clear();
|
|
||||||
layers.add(layer);
|
|
||||||
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -422,15 +288,10 @@ class BottomButton extends StatelessWidget {
|
||||||
/// Show image drawing surface over image
|
/// Show image drawing surface over image
|
||||||
class ImageEditorDrawing extends StatefulWidget {
|
class ImageEditorDrawing extends StatefulWidget {
|
||||||
final ImageItem image;
|
final ImageItem image;
|
||||||
final o.BrushOption options;
|
|
||||||
|
|
||||||
const ImageEditorDrawing({
|
const ImageEditorDrawing({
|
||||||
super.key,
|
super.key,
|
||||||
required this.image,
|
required this.image,
|
||||||
this.options = const o.BrushOption(
|
|
||||||
showBackground: true,
|
|
||||||
translatable: true,
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -438,9 +299,8 @@ class ImageEditorDrawing extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
|
class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
|
||||||
Color pickerColor = Colors.white,
|
Color pickerColor = Colors.white, currentColor = Colors.white;
|
||||||
currentColor = Colors.white,
|
|
||||||
currentBackgroundColor = Colors.black;
|
|
||||||
var screenshotController = ScreenshotController();
|
var screenshotController = ScreenshotController();
|
||||||
|
|
||||||
final control = HandSignatureControl(
|
final control = HandSignatureControl(
|
||||||
|
|
@ -452,12 +312,12 @@ class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
|
||||||
List<CubicPath> undoList = [];
|
List<CubicPath> undoList = [];
|
||||||
bool skipNextEvent = false;
|
bool skipNextEvent = false;
|
||||||
|
|
||||||
void changeColor(o.BrushColor color) {
|
// void changeColor(Colors color) {
|
||||||
currentColor = color.color;
|
// currentColor = color.color;
|
||||||
currentBackgroundColor = color.background;
|
// currentBackgroundColor = color.background;
|
||||||
|
|
||||||
setState(() {});
|
// setState(() {});
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -478,186 +338,156 @@ class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Theme(
|
final colors = [
|
||||||
data: ImageEditor.theme,
|
Colors.black,
|
||||||
child: Scaffold(
|
Colors.white,
|
||||||
appBar: AppBar(
|
Colors.blue,
|
||||||
automaticallyImplyLeading: false,
|
Colors.green,
|
||||||
actions: [
|
Colors.pink,
|
||||||
IconButton(
|
Colors.purple,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
Colors.brown,
|
||||||
icon: const Icon(Icons.clear),
|
Colors.indigo,
|
||||||
onPressed: () {
|
];
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
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());
|
return Scaffold(
|
||||||
setState(() {});
|
backgroundColor: Colors.red.withAlpha(0),
|
||||||
},
|
body: SafeArea(
|
||||||
),
|
child: Stack(
|
||||||
IconButton(
|
fit: StackFit.expand,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
children: [
|
||||||
icon: const Icon(Icons.check),
|
Positioned.fill(
|
||||||
onPressed: () async {
|
top: 0,
|
||||||
if (control.paths.isEmpty) return Navigator.pop(context);
|
child: Container(
|
||||||
|
height: 600,
|
||||||
if (widget.options.translatable) {
|
width: 300,
|
||||||
var data = await control.toImage(
|
decoration: BoxDecoration(
|
||||||
color: currentColor,
|
color: const Color.fromARGB(0, 210, 7, 7),
|
||||||
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);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Screenshot(
|
|
||||||
controller: screenshotController,
|
|
||||||
child: Container(
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color:
|
|
||||||
widget.options.showBackground ? null : currentBackgroundColor,
|
|
||||||
image: widget.options.showBackground
|
|
||||||
? DecorationImage(
|
|
||||||
image: Image.memory(widget.image.bytes).image,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
child: HandSignature(
|
|
||||||
control: control,
|
|
||||||
color: currentColor,
|
|
||||||
width: 1.0,
|
|
||||||
maxWidth: 7.0,
|
|
||||||
type: SignatureDrawType.shape,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottomNavigationBar: SafeArea(
|
|
||||||
child: Container(
|
|
||||||
height: 80,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(blurRadius: 2),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: ListView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
children: <Widget>[
|
|
||||||
ColorButton(
|
|
||||||
color: Colors.yellow,
|
|
||||||
onTap: (color) {
|
|
||||||
showModalBottomSheet(
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(10),
|
|
||||||
topLeft: Radius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
context: context,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black87,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(
|
|
||||||
MediaQuery.of(context).size.width / 2,
|
|
||||||
),
|
|
||||||
topRight: Radius.circular(
|
|
||||||
MediaQuery.of(context).size.width / 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: ColorPicker(
|
|
||||||
wheelDiameter:
|
|
||||||
MediaQuery.of(context).size.width - 64,
|
|
||||||
color: currentColor,
|
|
||||||
pickersEnabled: const {
|
|
||||||
ColorPickerType.both: false,
|
|
||||||
ColorPickerType.primary: false,
|
|
||||||
ColorPickerType.accent: false,
|
|
||||||
ColorPickerType.bw: false,
|
|
||||||
ColorPickerType.custom: false,
|
|
||||||
ColorPickerType.customSecondary: false,
|
|
||||||
ColorPickerType.wheel: true,
|
|
||||||
},
|
|
||||||
enableShadesSelection: false,
|
|
||||||
onColorChanged: (color) {
|
|
||||||
currentColor = color;
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
for (var color in widget.options.colors)
|
// child: Container(),
|
||||||
ColorButton(
|
child: Screenshot(
|
||||||
color: color.color,
|
controller: screenshotController,
|
||||||
onTap: (color) {
|
// image: widget.options.showBackground
|
||||||
currentColor = color;
|
// ? 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(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
isSelected: color.color == currentColor,
|
|
||||||
),
|
),
|
||||||
],
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Button used in bottomNavigationBar in ImageEditorDrawing
|
|
||||||
class ColorButton extends StatelessWidget {
|
class ColorButton extends StatelessWidget {
|
||||||
final Color color;
|
final Color color;
|
||||||
final Function(Color) onTap;
|
final Function(Color) onTap;
|
||||||
|
|
@ -677,15 +507,15 @@ class ColorButton extends StatelessWidget {
|
||||||
onTap(color);
|
onTap(color);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 34,
|
height: 17,
|
||||||
width: 34,
|
width: 17,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 23),
|
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: color,
|
color: color,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isSelected ? Colors.white : Colors.white54,
|
color: isSelected ? Colors.white : Colors.white54,
|
||||||
width: isSelected ? 3 : 1,
|
width: isSelected ? 2 : 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
|
||||||
|
|
||||||
/// Image layer to blur background using BackdropFilter
|
|
||||||
class BackgroundBlurLayer extends StatefulWidget {
|
|
||||||
final BackgroundBlurLayerData layerData;
|
|
||||||
final VoidCallback? onUpdate;
|
|
||||||
final bool editable;
|
|
||||||
|
|
||||||
const BackgroundBlurLayer({
|
|
||||||
super.key,
|
|
||||||
required this.layerData,
|
|
||||||
this.onUpdate,
|
|
||||||
this.editable = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<BackgroundBlurLayer> createState() => _BackgroundBlurLayerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BackgroundBlurLayerState extends State<BackgroundBlurLayer> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Positioned.fill(
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(
|
|
||||||
sigmaX: widget.layerData.radius,
|
|
||||||
sigmaY: widget.layerData.radius,
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
color: widget.layerData.color
|
|
||||||
.withAlpha((widget.layerData.opacity * 100).toInt()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
||||||
import 'package:twonly/src/components/image_editor/image_editor.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/modules/emoji_layer_overlay.dart';
|
|
||||||
|
|
||||||
/// Emoji layer
|
/// Emoji layer
|
||||||
class EmojiLayer extends StatefulWidget {
|
class EmojiLayer extends StatefulWidget {
|
||||||
|
|
@ -33,30 +31,7 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
left: widget.layerData.offset.dx,
|
left: widget.layerData.offset.dx,
|
||||||
top: widget.layerData.offset.dy,
|
top: widget.layerData.offset.dy,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: widget.editable
|
onTap: widget.editable ? () {} : null,
|
||||||
? () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(10),
|
|
||||||
topLeft: Radius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
context: context,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (context) {
|
|
||||||
return EmojiLayerOverlay(
|
|
||||||
index: layers.indexOf(widget.layerData),
|
|
||||||
layer: widget.layerData,
|
|
||||||
onUpdate: () {
|
|
||||||
if (widget.onUpdate != null) widget.onUpdate!();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
onScaleUpdate: widget.editable
|
onScaleUpdate: widget.editable
|
||||||
? (detail) {
|
? (detail) {
|
||||||
if (detail.pointerCount == 1) {
|
if (detail.pointerCount == 1) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
||||||
import 'package:twonly/src/components/image_editor/image_editor.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/modules/image_layer_overlay.dart';
|
|
||||||
|
|
||||||
/// Image layer that can be used to add overlay images and drawings
|
/// Image layer that can be used to add overlay images and drawings
|
||||||
class ImageLayer extends StatefulWidget {
|
class ImageLayer extends StatefulWidget {
|
||||||
|
|
@ -29,73 +27,11 @@ class _ImageLayerState extends State<ImageLayer> {
|
||||||
initialSize = widget.layerData.size;
|
initialSize = widget.layerData.size;
|
||||||
initialRotation = widget.layerData.rotation;
|
initialRotation = widget.layerData.rotation;
|
||||||
|
|
||||||
return Positioned(
|
return Positioned.fill(
|
||||||
left: widget.layerData.offset.dx,
|
child: SizedBox(
|
||||||
top: widget.layerData.offset.dy,
|
width: widget.layerData.image.width.toDouble(),
|
||||||
child: GestureDetector(
|
height: widget.layerData.image.height.toDouble(),
|
||||||
onTap: widget.editable
|
child: Image.memory(widget.layerData.image.bytes),
|
||||||
? () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(10),
|
|
||||||
topLeft: Radius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
context: context,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (context) {
|
|
||||||
return ImageLayerOverlay(
|
|
||||||
index: layers.indexOf(widget.layerData),
|
|
||||||
layerData: widget.layerData,
|
|
||||||
onUpdate: () {
|
|
||||||
if (widget.onUpdate != null) widget.onUpdate!();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: 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.scale = detail.scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: Transform(
|
|
||||||
transform: Matrix4(
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
1 / widget.layerData.scale,
|
|
||||||
),
|
|
||||||
child: SizedBox(
|
|
||||||
width: widget.layerData.image.width.toDouble(),
|
|
||||||
height: widget.layerData.image.height.toDouble(),
|
|
||||||
child: Image.memory(widget.layerData.image.bytes),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
||||||
import 'package:twonly/src/components/image_editor/image_editor.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/modules/text_layer_overlay.dart';
|
|
||||||
|
|
||||||
/// Text layer
|
/// Text layer
|
||||||
class TextLayer extends StatefulWidget {
|
class TextLayer extends StatefulWidget {
|
||||||
|
|
@ -20,79 +18,38 @@ class TextLayer extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TextViewState extends State<TextLayer> {
|
class _TextViewState extends State<TextLayer> {
|
||||||
double initialSize = 0;
|
|
||||||
double initialRotation = 0;
|
double initialRotation = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
initialSize = widget.layerData.size;
|
|
||||||
initialRotation = widget.layerData.rotation;
|
|
||||||
|
|
||||||
return Positioned(
|
return Positioned(
|
||||||
left: widget.layerData.offset.dx,
|
left: 0,
|
||||||
|
right: 0,
|
||||||
top: widget.layerData.offset.dy,
|
top: widget.layerData.offset.dy,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: widget.editable
|
onTap: widget.editable ? () {} : null,
|
||||||
? () {
|
|
||||||
showModalBottomSheet(
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(10),
|
|
||||||
topLeft: Radius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
context: context,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (context) {
|
|
||||||
return TextLayerOverlay(
|
|
||||||
index: layers.indexOf(widget.layerData),
|
|
||||||
layer: widget.layerData,
|
|
||||||
onUpdate: () {
|
|
||||||
if (widget.onUpdate != null) widget.onUpdate!();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
onScaleUpdate: widget.editable
|
onScaleUpdate: widget.editable
|
||||||
? (detail) {
|
? (detail) {
|
||||||
if (detail.pointerCount == 1) {
|
if (detail.pointerCount == 1) {
|
||||||
widget.layerData.offset = Offset(
|
widget.layerData.offset = Offset(
|
||||||
widget.layerData.offset.dx + detail.focalPointDelta.dx,
|
0,
|
||||||
widget.layerData.offset.dy + detail.focalPointDelta.dy,
|
widget.layerData.offset.dy + detail.focalPointDelta.dy,
|
||||||
);
|
);
|
||||||
} else if (detail.pointerCount == 2) {
|
|
||||||
widget.layerData.size =
|
|
||||||
initialSize + detail.scale * (detail.scale > 1 ? 1 : -1);
|
|
||||||
|
|
||||||
// print('angle');
|
|
||||||
// print(detail.rotation);
|
|
||||||
widget.layerData.rotation = detail.rotation;
|
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
child: Transform.rotate(
|
child: Container(
|
||||||
angle: widget.layerData.rotation,
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
child: Container(
|
decoration: BoxDecoration(
|
||||||
padding: const EdgeInsets.all(64),
|
color: Colors.black.withAlpha(100),
|
||||||
child: Container(
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
child: Text(
|
||||||
decoration: BoxDecoration(
|
widget.layerData.text.toString(),
|
||||||
color: widget.layerData.background
|
textAlign: TextAlign.center,
|
||||||
.withOpacity(widget.layerData.backgroundOpacity),
|
style: TextStyle(
|
||||||
borderRadius: BorderRadius.circular(8),
|
color: Colors.white,
|
||||||
),
|
fontSize: 20,
|
||||||
child: Text(
|
|
||||||
widget.layerData.text.toString(),
|
|
||||||
textAlign: widget.layerData.align,
|
|
||||||
style: TextStyle(
|
|
||||||
color: widget.layerData.color,
|
|
||||||
fontSize: widget.layerData.size,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
||||||
import 'package:twonly/src/components/image_editor/layers/background_blur_layer.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/layers/background_layer.dart';
|
import 'package:twonly/src/components/image_editor/layers/background_layer.dart';
|
||||||
import 'package:twonly/src/components/image_editor/layers/emoji_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/image_layer.dart';
|
||||||
|
|
@ -42,15 +41,6 @@ class LayersViewer extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background blur layer
|
|
||||||
if (layerItem is BackgroundBlurLayerData && layerItem.radius > 0) {
|
|
||||||
return BackgroundBlurLayer(
|
|
||||||
layerData: layerItem,
|
|
||||||
onUpdate: onUpdate,
|
|
||||||
editable: editable,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emoji layer
|
// Emoji layer
|
||||||
if (layerItem is EmojiLayerData) {
|
if (layerItem is EmojiLayerData) {
|
||||||
return EmojiLayer(
|
return EmojiLayer(
|
||||||
|
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
LoadingScreenHandler showLoadingScreen(
|
|
||||||
BuildContext context, {
|
|
||||||
String? text,
|
|
||||||
Color? color,
|
|
||||||
}) {
|
|
||||||
var handler = LoadingScreenHandler(
|
|
||||||
color: color,
|
|
||||||
text: text,
|
|
||||||
context: context,
|
|
||||||
);
|
|
||||||
|
|
||||||
showDialog<String>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) => LoadingScreenBody(
|
|
||||||
handler: handler,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoadingScreen {
|
|
||||||
final Color? color;
|
|
||||||
final GlobalKey<NavigatorState> globalKey;
|
|
||||||
|
|
||||||
LoadingScreen({
|
|
||||||
this.color,
|
|
||||||
required this.globalKey,
|
|
||||||
});
|
|
||||||
|
|
||||||
LoadingScreenHandler show({
|
|
||||||
String? text,
|
|
||||||
}) {
|
|
||||||
return showLoadingScreen(
|
|
||||||
globalKey.currentContext!,
|
|
||||||
text: text,
|
|
||||||
color: color,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
|
||||||
class LoadingScreenHandler {
|
|
||||||
String? id, text;
|
|
||||||
Color? color;
|
|
||||||
double? _progress;
|
|
||||||
late void Function() refresh;
|
|
||||||
BuildContext context;
|
|
||||||
bool expired = false;
|
|
||||||
|
|
||||||
LoadingScreenHandler({
|
|
||||||
required this.context,
|
|
||||||
this.id,
|
|
||||||
this.color,
|
|
||||||
this.text,
|
|
||||||
double? progress,
|
|
||||||
void Function()? refresh,
|
|
||||||
}) {
|
|
||||||
this.refresh = refresh ?? () {};
|
|
||||||
this.progress = progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
double? get progress => _progress;
|
|
||||||
set progress(double? value) {
|
|
||||||
_progress = value;
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
hide() {
|
|
||||||
if (expired) return;
|
|
||||||
|
|
||||||
expired = true;
|
|
||||||
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@protected
|
|
||||||
class LoadingScreenBody extends StatefulWidget {
|
|
||||||
final LoadingScreenHandler handler;
|
|
||||||
const LoadingScreenBody({super.key, required this.handler});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<LoadingScreenBody> createState() => _LoadingScreenBodyState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LoadingScreenBodyState extends State<LoadingScreenBody> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
widget.handler.refresh = () {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
widget.handler.context = context;
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: const Color.fromRGBO(0, 0, 0, 0),
|
|
||||||
body: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
CircularProgressIndicator(
|
|
||||||
color: widget.handler.color ?? Colors.white,
|
|
||||||
value: widget.handler.progress,
|
|
||||||
semanticsLabel: widget.handler.text,
|
|
||||||
),
|
|
||||||
if (widget.handler.progress != null) const SizedBox(height: 8),
|
|
||||||
if (widget.handler.progress != null)
|
|
||||||
Text(
|
|
||||||
'${(widget.handler.progress! * 100).toStringAsFixed(2)}%',
|
|
||||||
style: TextStyle(
|
|
||||||
color: widget.handler.color ?? Colors.white,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/components/image_editor/data/data.dart';
|
import 'package:twonly/src/components/image_editor/data/data.dart';
|
||||||
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
||||||
import 'package:twonly/src/components/image_editor/image_editor.dart';
|
|
||||||
|
|
||||||
class Emojies extends StatefulWidget {
|
class Emojis extends StatefulWidget {
|
||||||
const Emojies({super.key});
|
const Emojis({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
createState() => _EmojiesState();
|
createState() => _EmojisState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EmojiesState extends State<Emojies> {
|
class _EmojisState extends State<Emojis> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
|
|
@ -35,7 +34,7 @@ class _EmojiesState extends State<Emojies> {
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
Text(
|
Text(
|
||||||
i18n('Select Emoji'),
|
('Select Emoji'),
|
||||||
style: const TextStyle(color: Colors.white),
|
style: const TextStyle(color: Colors.white),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/image_editor.dart';
|
|
||||||
import 'colors_picker.dart';
|
|
||||||
|
|
||||||
class ColorPickersSlider extends StatefulWidget {
|
|
||||||
const ColorPickersSlider({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
createState() => _ColorPickersSliderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ColorPickersSliderState extends State<ColorPickersSlider> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(10), topLeft: Radius.circular(10)),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(20),
|
|
||||||
height: 240,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
i18n('Slider Filter Color').toUpperCase(),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Text(i18n('Slider Color'),
|
|
||||||
style: const TextStyle(color: Colors.white)),
|
|
||||||
// const SizedBox(height: 10),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: BarColorPicker(
|
|
||||||
width: 300,
|
|
||||||
thumbColor: Colors.white,
|
|
||||||
cornerRadius: 10,
|
|
||||||
pickMode: PickMode.color,
|
|
||||||
colorListener: (int value) {
|
|
||||||
setState(() {
|
|
||||||
// currentColor = Color(value);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {},
|
|
||||||
child: Text(i18n('Reset'),
|
|
||||||
style: const TextStyle(color: Colors.white)),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(i18n('Slider Opicity'),
|
|
||||||
style: const TextStyle(color: Colors.white)),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Row(children: [
|
|
||||||
Expanded(
|
|
||||||
child: Slider(
|
|
||||||
value: 0.1,
|
|
||||||
min: 0.0,
|
|
||||||
max: 1.0,
|
|
||||||
onChanged: (v) {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {},
|
|
||||||
child: Text(i18n('Reset'),
|
|
||||||
style: const TextStyle(color: Colors.white)),
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,353 +0,0 @@
|
||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
enum PickMode {
|
|
||||||
color,
|
|
||||||
grey,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A listener which receives an color in int representation. as used
|
|
||||||
/// by [BarColorPicker.colorListener] and [CircleColorPicker.colorListener].
|
|
||||||
typedef ColorListener = void Function(int value);
|
|
||||||
|
|
||||||
/// Constant color of thumb shadow
|
|
||||||
const _kThumbShadowColor = Color(0x44000000);
|
|
||||||
|
|
||||||
/// A padding used to calculate bar height(thumbRadius * 2 - kBarPadding).
|
|
||||||
const _kBarPadding = 4;
|
|
||||||
|
|
||||||
/// A bar color picker
|
|
||||||
class BarColorPicker extends StatefulWidget {
|
|
||||||
/// mode enum of pick a normal color or pick a grey color
|
|
||||||
final PickMode pickMode;
|
|
||||||
|
|
||||||
/// width of bar, if this widget is horizontal, than
|
|
||||||
/// bar width is this value, if this widget is vertical
|
|
||||||
/// bar height is this value
|
|
||||||
final double width;
|
|
||||||
|
|
||||||
/// A listener receives color pick events.
|
|
||||||
final ColorListener colorListener;
|
|
||||||
|
|
||||||
/// corner radius of the picker bar, for each corners
|
|
||||||
final double cornerRadius;
|
|
||||||
|
|
||||||
/// specifies the bar orientation
|
|
||||||
final bool horizontal;
|
|
||||||
|
|
||||||
/// thumb fill color
|
|
||||||
final Color thumbColor;
|
|
||||||
|
|
||||||
/// radius of thumb
|
|
||||||
final double thumbRadius;
|
|
||||||
|
|
||||||
/// initial color of this color picker.
|
|
||||||
final Color initialColor;
|
|
||||||
|
|
||||||
const BarColorPicker({
|
|
||||||
super.key,
|
|
||||||
this.pickMode = PickMode.color,
|
|
||||||
this.horizontal = true,
|
|
||||||
this.width = 200,
|
|
||||||
this.cornerRadius = 0.0,
|
|
||||||
this.thumbRadius = 8,
|
|
||||||
this.initialColor = const Color(0xffff0000),
|
|
||||||
this.thumbColor = Colors.black,
|
|
||||||
required this.colorListener,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
createState() => _BarColorPickerState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _BarColorPickerState extends State<BarColorPicker> {
|
|
||||||
double percent = 0.0;
|
|
||||||
late List<Color> colors;
|
|
||||||
late double barWidth, barHeight;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
if (widget.horizontal) {
|
|
||||||
barWidth = widget.width;
|
|
||||||
barHeight = widget.thumbRadius * 2 - _kBarPadding;
|
|
||||||
} else {
|
|
||||||
barWidth = widget.thumbRadius * 2 - _kBarPadding;
|
|
||||||
barHeight = widget.width;
|
|
||||||
}
|
|
||||||
switch (widget.pickMode) {
|
|
||||||
case PickMode.color:
|
|
||||||
colors = const [
|
|
||||||
Color(0xffff0000),
|
|
||||||
Color(0xffffff00),
|
|
||||||
Color(0xff00ff00),
|
|
||||||
Color(0xff00ffff),
|
|
||||||
Color(0xff0000ff),
|
|
||||||
Color(0xffff00ff),
|
|
||||||
Color(0xffff0000)
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
case PickMode.grey:
|
|
||||||
colors = const [Color(0xff000000), Color(0xffffffff)];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
percent = HSVColor.fromColor(widget.initialColor).hue / 360;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final thumbRadius = widget.thumbRadius;
|
|
||||||
final horizontal = widget.horizontal;
|
|
||||||
|
|
||||||
double? thumbLeft, thumbTop;
|
|
||||||
if (horizontal) {
|
|
||||||
thumbLeft = barWidth * percent;
|
|
||||||
} else {
|
|
||||||
thumbTop = barHeight * percent;
|
|
||||||
}
|
|
||||||
// build thumb
|
|
||||||
var thumb = Positioned(
|
|
||||||
left: thumbLeft,
|
|
||||||
top: thumbTop,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
width: thumbRadius * 2,
|
|
||||||
height: thumbRadius * 2,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(thumbRadius),
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: _kThumbShadowColor,
|
|
||||||
spreadRadius: 2,
|
|
||||||
blurRadius: 3,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
color: widget.thumbColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// build frame
|
|
||||||
double frameWidth, frameHeight;
|
|
||||||
if (horizontal) {
|
|
||||||
frameWidth = barWidth + thumbRadius * 2;
|
|
||||||
frameHeight = thumbRadius * 2;
|
|
||||||
} else {
|
|
||||||
frameWidth = thumbRadius * 2;
|
|
||||||
frameHeight = barHeight + thumbRadius * 2;
|
|
||||||
}
|
|
||||||
Widget frame = SizedBox(width: frameWidth, height: frameHeight);
|
|
||||||
|
|
||||||
// build content
|
|
||||||
Gradient gradient;
|
|
||||||
double left, top;
|
|
||||||
if (horizontal) {
|
|
||||||
gradient = LinearGradient(colors: colors);
|
|
||||||
left = thumbRadius;
|
|
||||||
top = (thumbRadius * 2 - barHeight) / 2;
|
|
||||||
} else {
|
|
||||||
gradient = LinearGradient(
|
|
||||||
colors: colors,
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter);
|
|
||||||
left = (thumbRadius * 2 - barWidth) / 2;
|
|
||||||
top = thumbRadius;
|
|
||||||
}
|
|
||||||
var content = Positioned(
|
|
||||||
left: left,
|
|
||||||
top: top,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
width: barWidth,
|
|
||||||
height: barHeight,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(widget.cornerRadius),
|
|
||||||
gradient: gradient,
|
|
||||||
),
|
|
||||||
child: const Text(''),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
onPanDown: (details) => handleTouch(details.globalPosition, context),
|
|
||||||
onPanStart: (details) => handleTouch(details.globalPosition, context),
|
|
||||||
onPanUpdate: (details) => handleTouch(details.globalPosition, context),
|
|
||||||
child: Stack(children: [frame, content, thumb]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// calculate colors picked from palette and update our states.
|
|
||||||
void handleTouch(Offset globalPosition, BuildContext context) {
|
|
||||||
var box = context.findRenderObject() as RenderBox;
|
|
||||||
var localPosition = box.globalToLocal(globalPosition);
|
|
||||||
double percent;
|
|
||||||
if (widget.horizontal) {
|
|
||||||
percent = (localPosition.dx - widget.thumbRadius) / barWidth;
|
|
||||||
} else {
|
|
||||||
percent = (localPosition.dy - widget.thumbRadius) / barHeight;
|
|
||||||
}
|
|
||||||
percent = min(max(0.0, percent), 1.0);
|
|
||||||
setState(() {
|
|
||||||
this.percent = percent;
|
|
||||||
});
|
|
||||||
switch (widget.pickMode) {
|
|
||||||
case PickMode.color:
|
|
||||||
var color = HSVColor.fromAHSV(1.0, percent * 360, 1.0, 1.0).toColor();
|
|
||||||
widget.colorListener(color.value);
|
|
||||||
break;
|
|
||||||
case PickMode.grey:
|
|
||||||
final channel = (0xff * percent).toInt();
|
|
||||||
widget.colorListener(
|
|
||||||
Color.fromARGB(0xff, channel, channel, channel).value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A circle palette color picker.
|
|
||||||
class CircleColorPicker extends StatefulWidget {
|
|
||||||
// radius of the color palette, note that radius * 2 is not the final
|
|
||||||
// width of this widget, instead is (radius + thumbRadius) * 2.
|
|
||||||
final double radius;
|
|
||||||
|
|
||||||
/// thumb fill color.
|
|
||||||
final Color thumbColor;
|
|
||||||
|
|
||||||
/// radius of thumb.
|
|
||||||
final double thumbRadius;
|
|
||||||
|
|
||||||
/// A listener receives color pick events.
|
|
||||||
final ColorListener colorListener;
|
|
||||||
|
|
||||||
/// initial color of this color picker.
|
|
||||||
final Color initialColor;
|
|
||||||
|
|
||||||
const CircleColorPicker({
|
|
||||||
super.key,
|
|
||||||
this.radius = 120,
|
|
||||||
this.initialColor = const Color(0xffff0000),
|
|
||||||
this.thumbColor = Colors.black,
|
|
||||||
this.thumbRadius = 8,
|
|
||||||
required this.colorListener,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<CircleColorPicker> createState() {
|
|
||||||
return _CircleColorPickerState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CircleColorPickerState extends State<CircleColorPicker> {
|
|
||||||
static const List<Color> colors = [
|
|
||||||
Color(0xffff0000),
|
|
||||||
Color(0xffffff00),
|
|
||||||
Color(0xff00ff00),
|
|
||||||
Color(0xff00ffff),
|
|
||||||
Color(0xff0000ff),
|
|
||||||
Color(0xffff00ff),
|
|
||||||
Color(0xffff0000)
|
|
||||||
];
|
|
||||||
|
|
||||||
late double thumbDistanceToCenter;
|
|
||||||
late double thumbRadians;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
thumbDistanceToCenter = widget.radius;
|
|
||||||
final hue = HSVColor.fromColor(widget.initialColor).hue;
|
|
||||||
thumbRadians = degreesToRadians(270 - hue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final radius = widget.radius;
|
|
||||||
final thumbRadius = widget.thumbRadius;
|
|
||||||
|
|
||||||
// compute thumb center coordinate
|
|
||||||
final thumbCenterX = radius + thumbDistanceToCenter * sin(thumbRadians);
|
|
||||||
final thumbCenterY = radius + thumbDistanceToCenter * cos(thumbRadians);
|
|
||||||
|
|
||||||
// build thumb widget
|
|
||||||
Widget thumb = Positioned(
|
|
||||||
child: Positioned(
|
|
||||||
left: thumbCenterX,
|
|
||||||
top: thumbCenterY,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
width: thumbRadius * 2,
|
|
||||||
height: thumbRadius * 2,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
boxShadow: const [
|
|
||||||
BoxShadow(
|
|
||||||
color: _kThumbShadowColor,
|
|
||||||
spreadRadius: 2,
|
|
||||||
blurRadius: 3,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
borderRadius: BorderRadius.circular(thumbRadius),
|
|
||||||
color: widget.thumbColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onPanDown: (details) => handleTouch(details.globalPosition, context),
|
|
||||||
onPanStart: (details) => handleTouch(details.globalPosition, context),
|
|
||||||
onPanUpdate: (details) => handleTouch(details.globalPosition, context),
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: (radius + thumbRadius) * 2,
|
|
||||||
height: (radius + thumbRadius) * 2),
|
|
||||||
Positioned(
|
|
||||||
left: thumbRadius,
|
|
||||||
top: thumbRadius,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
width: radius * 2,
|
|
||||||
height: radius * 2,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(radius),
|
|
||||||
gradient: const SweepGradient(colors: colors),
|
|
||||||
),
|
|
||||||
child: const Text(''),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
thumb
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// calculate colors picked from palette and update our states.
|
|
||||||
void handleTouch(Offset globalPosition, BuildContext context) {
|
|
||||||
var box = context.findRenderObject() as RenderBox;
|
|
||||||
var localPosition = box.globalToLocal(globalPosition);
|
|
||||||
final centerX = box.size.width / 2;
|
|
||||||
final centerY = box.size.height / 2;
|
|
||||||
final deltaX = localPosition.dx - centerX;
|
|
||||||
final deltaY = localPosition.dy - centerY;
|
|
||||||
final distanceToCenter = sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
||||||
var theta = atan2(deltaX, deltaY);
|
|
||||||
var degree = 270 - radiansToDegrees(theta);
|
|
||||||
if (degree < 0) degree = 360 + degree;
|
|
||||||
widget.colorListener(HSVColor.fromAHSV(1, degree, 1, 1).toColor().value);
|
|
||||||
setState(() {
|
|
||||||
thumbDistanceToCenter = min(distanceToCenter, widget.radius);
|
|
||||||
thumbRadians = theta;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// convert an angle value from radian to degree representation.
|
|
||||||
double radiansToDegrees(double radians) {
|
|
||||||
return (radians + pi) / pi * 180;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// convert an angle value from degree to radian representation.
|
|
||||||
double degreesToRadians(double degrees) {
|
|
||||||
return degrees / 180 * pi - pi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/image_editor.dart';
|
|
||||||
|
|
||||||
class EmojiLayerOverlay extends StatefulWidget {
|
|
||||||
final int index;
|
|
||||||
final EmojiLayerData layer;
|
|
||||||
final Function onUpdate;
|
|
||||||
|
|
||||||
const EmojiLayerOverlay({
|
|
||||||
super.key,
|
|
||||||
required this.layer,
|
|
||||||
required this.index,
|
|
||||||
required this.onUpdate,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
createState() => _EmojiLayerOverlayState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _EmojiLayerOverlayState extends State<EmojiLayerOverlay> {
|
|
||||||
double slider = 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
// slider = widget.sizevalue;
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
height: 200,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.black87,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(10), topLeft: Radius.circular(10)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
i18n('Size Adjust').toUpperCase(),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Slider(
|
|
||||||
activeColor: Colors.white,
|
|
||||||
inactiveColor: Colors.grey,
|
|
||||||
value: widget.layer.size,
|
|
||||||
min: 0.0,
|
|
||||||
max: 100.0,
|
|
||||||
onChangeEnd: (v) {
|
|
||||||
setState(() {
|
|
||||||
widget.layer.size = v.toDouble();
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onChanged: (v) {
|
|
||||||
setState(() {
|
|
||||||
slider = v;
|
|
||||||
// print(v.toDouble());
|
|
||||||
widget.layer.size = v.toDouble();
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Row(children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
removedLayers.add(layers.removeAt(widget.index));
|
|
||||||
Navigator.pop(context);
|
|
||||||
widget.onUpdate();
|
|
||||||
// back(context);
|
|
||||||
// setState(() {});
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
i18n('Remove'),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/image_editor.dart';
|
|
||||||
|
|
||||||
class ImageLayerOverlay extends StatefulWidget {
|
|
||||||
final int index;
|
|
||||||
final ImageLayerData layerData;
|
|
||||||
final Function onUpdate;
|
|
||||||
|
|
||||||
const ImageLayerOverlay({
|
|
||||||
super.key,
|
|
||||||
required this.layerData,
|
|
||||||
required this.index,
|
|
||||||
required this.onUpdate,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
createState() => _ImageLayerOverlayState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ImageLayerOverlayState extends State<ImageLayerOverlay> {
|
|
||||||
double slider = 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
// slider = widget.sizevalue;
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
height: 200,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.black87,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(10), topLeft: Radius.circular(10)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
i18n('Size Adjust').toUpperCase(),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Slider(
|
|
||||||
activeColor: Colors.white,
|
|
||||||
inactiveColor: Colors.grey,
|
|
||||||
value: widget.layerData.scale,
|
|
||||||
min: 0,
|
|
||||||
max: 2,
|
|
||||||
divisions: 100,
|
|
||||||
onChangeEnd: (v) {
|
|
||||||
setState(() {
|
|
||||||
widget.layerData.scale = v.toDouble();
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onChanged: (v) {
|
|
||||||
setState(() {
|
|
||||||
slider = v;
|
|
||||||
// print(v.toDouble());
|
|
||||||
widget.layerData.scale = v.toDouble();
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Row(children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
removedLayers.add(layers.removeAt(widget.index));
|
|
||||||
Navigator.pop(context);
|
|
||||||
widget.onUpdate();
|
|
||||||
// back(context);
|
|
||||||
// setState(() {});
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
i18n('Remove'),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
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/components/image_editor/image_editor.dart';
|
|
||||||
|
|
||||||
class TextEditorImage extends StatefulWidget {
|
|
||||||
const TextEditorImage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
createState() => _TextEditorImageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TextEditorImageState extends State<TextEditorImage> {
|
|
||||||
TextEditingController name = TextEditingController();
|
|
||||||
Color currentColor = Colors.white;
|
|
||||||
double slider = 32.0;
|
|
||||||
TextAlign align = TextAlign.left;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
var size = MediaQuery.of(context).size;
|
|
||||||
|
|
||||||
return Theme(
|
|
||||||
data: ThemeData.dark(),
|
|
||||||
child: Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
actions: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(FontAwesomeIcons.alignLeft,
|
|
||||||
color: align == TextAlign.left
|
|
||||||
? Colors.white
|
|
||||||
: Colors.white.withAlpha(80)),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
align = TextAlign.left;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(FontAwesomeIcons.alignCenter,
|
|
||||||
color: align == TextAlign.center
|
|
||||||
? Colors.white
|
|
||||||
: Colors.white.withAlpha(80)),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
align = TextAlign.center;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(FontAwesomeIcons.alignRight,
|
|
||||||
color: align == TextAlign.right
|
|
||||||
? Colors.white
|
|
||||||
: Colors.white.withAlpha(80)),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
align = TextAlign.right;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.check),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(
|
|
||||||
context,
|
|
||||||
TextLayerData(
|
|
||||||
background: Colors.transparent,
|
|
||||||
text: name.text,
|
|
||||||
color: currentColor,
|
|
||||||
size: slider.toDouble(),
|
|
||||||
align: align,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
color: Colors.white,
|
|
||||||
padding: const EdgeInsets.all(15),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(children: [
|
|
||||||
SizedBox(
|
|
||||||
height: size.height / 2.2,
|
|
||||||
child: TextField(
|
|
||||||
controller: name,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
contentPadding: const EdgeInsets.all(10),
|
|
||||||
hintText: i18n('Insert Your Message'),
|
|
||||||
hintStyle: const TextStyle(color: Colors.white),
|
|
||||||
alignLabelWithHint: true,
|
|
||||||
),
|
|
||||||
scrollPadding: const EdgeInsets.all(20.0),
|
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
minLines: 5,
|
|
||||||
maxLines: 99999,
|
|
||||||
style: TextStyle(
|
|
||||||
color: currentColor,
|
|
||||||
),
|
|
||||||
autofocus: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
|
||||||
import 'package:twonly/src/components/image_editor/image_editor.dart';
|
|
||||||
import 'colors_picker.dart';
|
|
||||||
|
|
||||||
class TextLayerOverlay extends StatefulWidget {
|
|
||||||
final int index;
|
|
||||||
final TextLayerData layer;
|
|
||||||
final Function onUpdate;
|
|
||||||
|
|
||||||
const TextLayerOverlay({
|
|
||||||
super.key,
|
|
||||||
required this.layer,
|
|
||||||
required this.index,
|
|
||||||
required this.onUpdate,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
createState() => _TextLayerOverlayState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TextLayerOverlayState extends State<TextLayerOverlay> {
|
|
||||||
double slider = 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
// slider = widget.sizevalue;
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
height: 450,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.black87,
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topRight: Radius.circular(10),
|
|
||||||
topLeft: Radius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child:
|
|
||||||
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
child: Text(
|
|
||||||
i18n('Size'),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(children: [
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Slider(
|
|
||||||
thumbColor: Colors.white,
|
|
||||||
value: widget.layer.size,
|
|
||||||
min: 0.0,
|
|
||||||
max: 100.0,
|
|
||||||
onChangeEnd: (v) {
|
|
||||||
setState(() {
|
|
||||||
widget.layer.size = v.toDouble();
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onChanged: (v) {
|
|
||||||
setState(() {
|
|
||||||
slider = v;
|
|
||||||
// print(v.toDouble());
|
|
||||||
widget.layer.size = v.toDouble();
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
widget.layer.backgroundOpacity = 0.5;
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
i18n('Reset'),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
child: Text(
|
|
||||||
i18n('Color'),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(children: [
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: BarColorPicker(
|
|
||||||
width: 300,
|
|
||||||
thumbColor: Colors.white,
|
|
||||||
initialColor: widget.layer.color,
|
|
||||||
cornerRadius: 10,
|
|
||||||
pickMode: PickMode.color,
|
|
||||||
colorListener: (int value) {
|
|
||||||
setState(() {
|
|
||||||
widget.layer.color = Color(value);
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
widget.layer.color = Colors.black;
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Text(i18n('Reset'),
|
|
||||||
style: const TextStyle(color: Colors.white)),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
child: Text(
|
|
||||||
i18n('Background Color'),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(children: [
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: BarColorPicker(
|
|
||||||
width: 300,
|
|
||||||
initialColor: widget.layer.background,
|
|
||||||
thumbColor: Colors.white,
|
|
||||||
cornerRadius: 10,
|
|
||||||
pickMode: PickMode.color,
|
|
||||||
colorListener: (int value) {
|
|
||||||
setState(() {
|
|
||||||
widget.layer.background = Color(value);
|
|
||||||
if (widget.layer.backgroundOpacity == 0) {
|
|
||||||
widget.layer.backgroundOpacity = 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
widget.layer.background = Colors.transparent;
|
|
||||||
widget.layer.backgroundOpacity = 0;
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
i18n('Reset'),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
]),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
child: Text(
|
|
||||||
i18n('Background Opacity'),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(children: [
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Slider(
|
|
||||||
min: 0,
|
|
||||||
max: 1,
|
|
||||||
divisions: 100,
|
|
||||||
value: widget.layer.backgroundOpacity,
|
|
||||||
thumbColor: Colors.white,
|
|
||||||
onChanged: (double value) {
|
|
||||||
setState(() {
|
|
||||||
widget.layer.backgroundOpacity = value;
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
widget.layer.backgroundOpacity = 0;
|
|
||||||
widget.onUpdate();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
i18n('Reset'),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Row(children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
removedLayers.add(layers.removeAt(widget.index));
|
|
||||||
|
|
||||||
Navigator.pop(context);
|
|
||||||
widget.onUpdate();
|
|
||||||
// back(context);
|
|
||||||
// setState(() {});
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
i18n('Remove'),
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class BrushOption {
|
|
||||||
/// show background image on draw screen
|
|
||||||
final bool showBackground;
|
|
||||||
|
|
||||||
/// User will able to move, zoom drawn image
|
|
||||||
/// Note: Layer may not be placed precisely
|
|
||||||
final bool translatable;
|
|
||||||
final List<BrushColor> colors;
|
|
||||||
|
|
||||||
const BrushOption({
|
|
||||||
this.showBackground = true,
|
|
||||||
this.translatable = false,
|
|
||||||
this.colors = const [
|
|
||||||
BrushColor(color: Colors.black, background: Colors.white),
|
|
||||||
BrushColor(color: Colors.white),
|
|
||||||
BrushColor(color: Colors.blue),
|
|
||||||
BrushColor(color: Colors.green),
|
|
||||||
BrushColor(color: Colors.pink),
|
|
||||||
BrushColor(color: Colors.purple),
|
|
||||||
BrushColor(color: Colors.brown),
|
|
||||||
BrushColor(color: Colors.indigo),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class BrushColor {
|
|
||||||
/// Color of brush
|
|
||||||
final Color color;
|
|
||||||
|
|
||||||
/// Background color while brush is active only be used when showBackground is false
|
|
||||||
final Color background;
|
|
||||||
|
|
||||||
const BrushColor({
|
|
||||||
required this.color,
|
|
||||||
this.background = Colors.black,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class EmojiOption {
|
|
||||||
const EmojiOption();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TextOption {
|
|
||||||
const TextOption();
|
|
||||||
}
|
|
||||||
18
pubspec.lock
18
pubspec.lock
|
|
@ -310,22 +310,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
flex_color_picker:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flex_color_picker
|
|
||||||
sha256: c083b79f1c57eaeed9f464368be376951230b3cb1876323b784626152a86e480
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.7.0"
|
|
||||||
flex_seed_scheme:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: flex_seed_scheme
|
|
||||||
sha256: d3ba3c5c92d2d79d45e94b4c6c71d01fac3c15017da1545880c53864da5dfeb0
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.5.0"
|
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -1233,4 +1217,4 @@ packages:
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.6.0 <4.0.0"
|
||||||
flutter: ">=3.27.0"
|
flutter: ">=3.24.0"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ dependencies:
|
||||||
connectivity_plus: ^6.1.2
|
connectivity_plus: ^6.1.2
|
||||||
cv: ^1.1.3
|
cv: ^1.1.3
|
||||||
fixnum: ^1.1.1
|
fixnum: ^1.1.1
|
||||||
flex_color_picker: ^3.7.0
|
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_image_compress: ^2.4.0
|
flutter_image_compress: ^2.4.0
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue