improve image editor

This commit is contained in:
otsmr 2025-02-02 01:37:10 +01:00
parent 9a162b5b2f
commit 4667ec09cc
21 changed files with 297 additions and 2022 deletions

View file

@ -1 +0,0 @@
The image editor is based on: https://github.com/hsbijarniya/image_editor_plus/tree/main

View file

@ -1,4 +0,0 @@
enum EditorMode {
brush,
filter,
}

View file

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

View file

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

View file

@ -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,15 +40,22 @@ 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,
child: Row(children: [
IconButton( IconButton(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
icon: Icon(Icons.undo, icon: Icon(Icons.undo,
@ -109,11 +68,8 @@ class _ImageEditorState extends State<ImageEditor> {
setState(() {}); setState(() {});
return; return;
} }
if (layers.length <= 1) return; // do not remove image layer if (layers.length <= 1) return; // do not remove image layer
undoLayers.add(layers.removeLast()); undoLayers.add(layers.removeLast());
setState(() {}); setState(() {});
}, },
), ),
@ -129,24 +85,7 @@ class _ImageEditorState extends State<ImageEditor> {
setState(() {}); setState(() {});
}, },
), ),
IconButton( const SizedBox(width: 50)
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);
},
),
]),
),
]; ];
} }
@ -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,36 +118,10 @@ 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) {
// print(details);
// move
if (details.pointerCount == 1) {
// print(details.focalPointDelta);
// x += details.focalPointDelta.dx;
// 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(() {});
}
}
},
onScaleEnd: (details) {
lastScaleFactor = scaleFactor;
},
child: SizedBox(
height: currentImage.height / pixelRatio, height: currentImage.height / pixelRatio,
width: currentImage.width / pixelRatio, width: currentImage.width / pixelRatio,
child: Screenshot( child: Screenshot(
@ -228,25 +135,19 @@ class _ImageEditorState extends State<ImageEditor> {
), ),
), ),
), ),
),
Positioned( Positioned(
top: 0, top: 5,
left: 0, left: 0,
right: 0, right: 0,
child: Container(
decoration: BoxDecoration(
color: Colors.black.withAlpha(75),
),
child: SafeArea( child: SafeArea(
child: Row( child: Row(
children: filterActions, 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,44 +161,37 @@ class _ImageEditorState extends State<ImageEditor> {
// ], // ],
), ),
child: SafeArea( child: SafeArea(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
if (widget.textOption != null)
BottomButton( BottomButton(
icon: Icons.text_fields, icon: FontAwesomeIcons.font,
onTap: () async { onTap: () async {
TextLayerData? layer = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TextEditorImage(),
),
);
if (layer == null) return;
undoLayers.clear(); undoLayers.clear();
removedLayers.clear(); removedLayers.clear();
layers.add(layer); layers.add(
TextLayerData(
text: "Test",
),
);
setState(() {}); setState(() {});
}, },
), ),
if (widget.brushOption != null) SizedBox(height: 20),
BottomButton( BottomButton(
icon: Icons.edit, icon: FontAwesomeIcons.pencil,
onTap: () async { onTap: () async {
if (widget.brushOption!.translatable) {
var drawing = await Navigator.push( var drawing = await Navigator.push(
context, context,
MaterialPageRoute( PageRouteBuilder(
builder: (context) => ImageEditorDrawing( opaque: false,
pageBuilder: (context, a, b) => ImageEditorDrawing(
image: currentImage, image: currentImage,
options: widget.brushOption!,
), ),
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
), ),
); );
@ -308,50 +202,23 @@ class _ImageEditorState extends State<ImageEditor> {
layers.add( layers.add(
ImageLayerData( ImageLayerData(
image: ImageItem(drawing), image: ImageItem(drawing),
offset: Offset( offset: Offset(0, 0),
-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) SizedBox(height: 20),
BottomButton( BottomButton(
icon: FontAwesomeIcons.faceSmile, icon: FontAwesomeIcons.faceGrinWide,
onTap: () async { onTap: () async {
EmojiLayerData? layer = await showModalBottomSheet( EmojiLayerData? layer = await showModalBottomSheet(
context: context, context: context,
backgroundColor: Colors.black, backgroundColor: Colors.black,
builder: (BuildContext context) { builder: (BuildContext context) {
return const Emojies(); return const Emojis();
}, },
); );
@ -369,7 +236,6 @@ class _ImageEditorState extends State<ImageEditor> {
), ),
), ),
), ),
),
]); ]);
} }
@ -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,12 +338,56 @@ 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,
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( IconButton(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
@ -491,7 +395,6 @@ class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
Navigator.pop(context); Navigator.pop(context);
}, },
), ),
const Spacer(),
IconButton( IconButton(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
icon: Icon( icon: Icon(
@ -529,7 +432,6 @@ class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
onPressed: () async { onPressed: () async {
if (control.paths.isEmpty) return Navigator.pop(context); if (control.paths.isEmpty) return Navigator.pop(context);
if (widget.options.translatable) {
var data = await control.toImage( var data = await control.toImage(
color: currentColor, color: currentColor,
height: widget.image.height, height: widget.image.height,
@ -539,125 +441,53 @@ class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
if (!mounted) return; if (!mounted) return;
return Navigator.pop(context, data!.buffer.asUint8List()); return Navigator.pop(context, data!.buffer.asUint8List());
}
var loadingScreen = showLoadingScreen(context); // var loadingScreen = showLoadingScreen(context);
var image = await screenshotController.capture(); // var image = await screenshotController.capture();
loadingScreen.hide(); // loadingScreen.hide();
if (!mounted) return; // if (!mounted) return;
return Navigator.pop(context, image); // return Navigator.pop(context, image);
}, },
), ),
], ],
), ),
body: Screenshot( ),
controller: screenshotController, Positioned(
right: 0,
top: 50,
child: Container( child: Container(
height: MediaQuery.of(context).size.height, child: Container(
width: MediaQuery.of(context).size.width, // height: 80,
padding: EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color: const Color.fromARGB(130, 0, 0, 0),
widget.options.showBackground ? null : currentBackgroundColor, borderRadius: BorderRadius.all(Radius.circular(10)),
image: widget.options.showBackground
? DecorationImage(
image: Image.memory(widget.image.bytes).image,
fit: BoxFit.contain,
)
: null,
), ),
child: HandSignature( child: Column(
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>[ children: <Widget>[
for (var color in colors)
ColorButton( ColorButton(
color: Colors.yellow, color: color,
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)
ColorButton(
color: color.color,
onTap: (color) { onTap: (color) {
currentColor = color; currentColor = color;
setState(() {}); setState(() {});
}, },
isSelected: color.color == currentColor, 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,
), ),
), ),
), ),

View file

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

View file

@ -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) {

View file

@ -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,74 +27,12 @@ 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,
top: widget.layerData.offset.dy,
child: GestureDetector(
onTap: widget.editable
? () {
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( child: SizedBox(
width: widget.layerData.image.width.toDouble(), width: widget.layerData.image.width.toDouble(),
height: widget.layerData.image.height.toDouble(), height: widget.layerData.image.height.toDouble(),
child: Image.memory(widget.layerData.image.bytes), child: Image.memory(widget.layerData.image.bytes),
), ),
),
),
); );
} }
} }

View file

@ -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(
angle: widget.layerData.rotation,
child: Container(
padding: const EdgeInsets.all(64),
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: widget.layerData.background color: Colors.black.withAlpha(100),
.withOpacity(widget.layerData.backgroundOpacity),
borderRadius: BorderRadius.circular(8),
), ),
child: Text( child: Text(
widget.layerData.text.toString(), widget.layerData.text.toString(),
textAlign: widget.layerData.align, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: widget.layerData.color, color: Colors.white,
fontSize: widget.layerData.size, fontSize: 20,
),
),
), ),
), ),
), ),

View file

@ -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(

View file

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

View file

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

View file

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

View file

@ -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;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"

View file

@ -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