mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 21:18:40 +00:00
improve draw editor
This commit is contained in:
parent
fdaa1ff7dd
commit
5f45de620a
9 changed files with 674 additions and 580 deletions
25
.vscode/launch.json
vendored
Normal file
25
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
// Verwendet IntelliSense zum Ermitteln möglicher Attribute.
|
||||||
|
// Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
|
||||||
|
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Connect-App",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Connect-App (profile mode)",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"flutterMode": "profile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Connect-App (release mode)",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"flutterMode": "release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
28
lib/src/components/image_editor/action_button.dart
Normal file
28
lib/src/components/image_editor/action_button.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
|
class ActionButton extends StatelessWidget {
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final IconData? icon;
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
const ActionButton(this.icon, {super.key, this.onPressed, this.color});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
icon: FaIcon(
|
||||||
|
icon,
|
||||||
|
size: 30,
|
||||||
|
color: color,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: const Color.fromARGB(122, 0, 0, 0),
|
||||||
|
blurRadius: 5.0,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onPressed: onPressed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hand_signature/signature.dart';
|
||||||
import 'package:twonly/src/components/image_editor/data/image_item.dart';
|
import 'package:twonly/src/components/image_editor/data/image_item.dart';
|
||||||
|
|
||||||
/// Layer class with some common properties
|
/// Layer class with some common properties
|
||||||
|
|
@ -7,12 +8,14 @@ class Layer {
|
||||||
double rotation, scale, opacity;
|
double rotation, scale, opacity;
|
||||||
bool isEditing;
|
bool isEditing;
|
||||||
bool isDeleted;
|
bool isDeleted;
|
||||||
|
bool hasCustomActionButtons;
|
||||||
|
|
||||||
Layer({
|
Layer({
|
||||||
this.offset = const Offset(0, 0),
|
this.offset = const Offset(0, 0),
|
||||||
this.opacity = 1,
|
this.opacity = 1,
|
||||||
this.isEditing = false,
|
this.isEditing = false,
|
||||||
this.isDeleted = false,
|
this.isDeleted = false,
|
||||||
|
this.hasCustomActionButtons = false,
|
||||||
this.rotation = 0,
|
this.rotation = 0,
|
||||||
this.scale = 1,
|
this.scale = 1,
|
||||||
});
|
});
|
||||||
|
|
@ -71,3 +74,22 @@ class TextLayerData extends Layer {
|
||||||
super.isEditing = true,
|
super.isEditing = true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attributes used by [DrawLayer]
|
||||||
|
class DrawLayerData extends Layer {
|
||||||
|
final control = HandSignatureControl(
|
||||||
|
threshold: 3.0,
|
||||||
|
smoothRatio: 0.65,
|
||||||
|
velocityRange: 2.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// String text;
|
||||||
|
DrawLayerData({
|
||||||
|
super.offset,
|
||||||
|
super.opacity,
|
||||||
|
super.rotation,
|
||||||
|
super.scale,
|
||||||
|
super.hasCustomActionButtons = true,
|
||||||
|
super.isEditing = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
200
lib/src/components/image_editor/layers/draw_layer.dart
Normal file
200
lib/src/components/image_editor/layers/draw_layer.dart
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:hand_signature/signature.dart';
|
||||||
|
import 'package:screenshot/screenshot.dart';
|
||||||
|
import 'package:twonly/src/components/image_editor/action_button.dart';
|
||||||
|
import 'package:twonly/src/components/image_editor/data/layer.dart';
|
||||||
|
|
||||||
|
class DrawLayer extends StatefulWidget {
|
||||||
|
final DrawLayerData layerData;
|
||||||
|
final VoidCallback? onUpdate;
|
||||||
|
|
||||||
|
const DrawLayer({
|
||||||
|
super.key,
|
||||||
|
required this.layerData,
|
||||||
|
this.onUpdate,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
createState() => _DrawLayerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DrawLayerState extends State<DrawLayer> {
|
||||||
|
Color pickerColor = Colors.white, currentColor = Colors.white;
|
||||||
|
|
||||||
|
var screenshotController = ScreenshotController();
|
||||||
|
|
||||||
|
List<CubicPath> undoList = [];
|
||||||
|
bool skipNextEvent = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
widget.layerData.control.addListener(() {
|
||||||
|
if (widget.layerData.control.hasActivePath) return;
|
||||||
|
|
||||||
|
if (skipNextEvent) {
|
||||||
|
skipNextEvent = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
undoList = [];
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
double _sliderValue = 0.0;
|
||||||
|
|
||||||
|
final colors = [
|
||||||
|
Colors.white,
|
||||||
|
Colors.red,
|
||||||
|
Colors.orange,
|
||||||
|
Colors.yellow,
|
||||||
|
Colors.green,
|
||||||
|
Colors.indigo,
|
||||||
|
Colors.blue,
|
||||||
|
Colors.black,
|
||||||
|
];
|
||||||
|
|
||||||
|
Color _getColorFromSliderValue(double value) {
|
||||||
|
// Calculate the index based on the slider value
|
||||||
|
int index = (value * (colors.length - 1)).floor();
|
||||||
|
int nextIndex = (index + 1).clamp(0, colors.length - 1);
|
||||||
|
|
||||||
|
// Calculate the interpolation factor
|
||||||
|
double factor = value * (colors.length - 1) - index;
|
||||||
|
|
||||||
|
// Interpolate between the two colors
|
||||||
|
return Color.lerp(colors[index], colors[nextIndex], factor)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSliderChanged(double value) {
|
||||||
|
setState(() {
|
||||||
|
_sliderValue = value;
|
||||||
|
currentColor = _getColorFromSliderValue(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: Screenshot(
|
||||||
|
controller: screenshotController,
|
||||||
|
child: HandSignature(
|
||||||
|
control: widget.layerData.control,
|
||||||
|
color: currentColor,
|
||||||
|
width: 1.0,
|
||||||
|
maxWidth: 7.0,
|
||||||
|
type: SignatureDrawType.shape,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.layerData.isEditing)
|
||||||
|
Positioned(
|
||||||
|
top: 5,
|
||||||
|
left: 5,
|
||||||
|
right: 50,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ActionButton(
|
||||||
|
FontAwesomeIcons.check,
|
||||||
|
onPressed: () async {
|
||||||
|
widget.layerData.isEditing = false;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(child: Container()),
|
||||||
|
ActionButton(
|
||||||
|
FontAwesomeIcons.arrowRotateLeft,
|
||||||
|
color: widget.layerData.control.paths.isNotEmpty
|
||||||
|
? Colors.white
|
||||||
|
: Colors.white.withAlpha(80),
|
||||||
|
onPressed: () {
|
||||||
|
if (widget.layerData.control.paths.isEmpty) return;
|
||||||
|
skipNextEvent = true;
|
||||||
|
undoList.add(widget.layerData.control.paths.last);
|
||||||
|
widget.layerData.control.stepBack();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ActionButton(
|
||||||
|
FontAwesomeIcons.arrowRotateRight,
|
||||||
|
color: undoList.isNotEmpty
|
||||||
|
? Colors.white
|
||||||
|
: Colors.white.withAlpha(80),
|
||||||
|
onPressed: () {
|
||||||
|
if (undoList.isEmpty) return;
|
||||||
|
|
||||||
|
widget.layerData.control.paths.add(undoList.removeLast());
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.layerData.isEditing)
|
||||||
|
Positioned(
|
||||||
|
right: 20,
|
||||||
|
top: 50,
|
||||||
|
child: Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
height: 240,
|
||||||
|
width: 40,
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 240,
|
||||||
|
width: 40,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
width: 10,
|
||||||
|
height: 195,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: colors,
|
||||||
|
stops: List.generate(colors.length,
|
||||||
|
(index) => index / (colors.length - 1)),
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned.fill(
|
||||||
|
child: RotatedBox(
|
||||||
|
quarterTurns: 1,
|
||||||
|
child: Slider(
|
||||||
|
value: _sliderValue,
|
||||||
|
thumbColor: currentColor,
|
||||||
|
activeColor: Colors.transparent,
|
||||||
|
inactiveColor: Colors.transparent,
|
||||||
|
onChanged: _onSliderChanged,
|
||||||
|
min: 0.0,
|
||||||
|
max: 1.0,
|
||||||
|
divisions: 100,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!widget.layerData.isEditing)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:twonly/src/components/image_editor/action_button.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/views/camera_to_share/share_image_editor_view.dart';
|
|
||||||
|
|
||||||
/// Text layer
|
/// Text layer
|
||||||
class TextLayer extends StatefulWidget {
|
class TextLayer extends StatefulWidget {
|
||||||
|
|
@ -148,9 +148,8 @@ class _TextViewState extends State<TextLayer> {
|
||||||
onTapUp: (d) {
|
onTapUp: (d) {
|
||||||
textController.text = "";
|
textController.text = "";
|
||||||
},
|
},
|
||||||
child: FaIcon(
|
child: ActionButton(
|
||||||
FontAwesomeIcons.trashCan,
|
FontAwesomeIcons.trashCan,
|
||||||
shadows: ShareImageEditorView.iconShadow,
|
|
||||||
color: deleteLayer ? Colors.red : Colors.white,
|
color: deleteLayer ? Colors.red : Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
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_layer.dart';
|
import 'package:twonly/src/components/image_editor/layers/background_layer.dart';
|
||||||
|
import 'package:twonly/src/components/image_editor/layers/draw_layer.dart';
|
||||||
import 'package:twonly/src/components/image_editor/layers/emoji_layer.dart';
|
import 'package:twonly/src/components/image_editor/layers/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';
|
||||||
import 'package:twonly/src/components/image_editor/layers/text_layer.dart';
|
import 'package:twonly/src/components/image_editor/layers/text_layer.dart';
|
||||||
|
|
@ -20,42 +21,53 @@ class LayersViewer extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: layers.map((layerItem) {
|
children: [
|
||||||
// Background layer
|
// Background and Image layers at the bottom
|
||||||
|
...layers
|
||||||
|
.where((layerItem) =>
|
||||||
|
layerItem is BackgroundLayerData || layerItem is ImageLayerData)
|
||||||
|
.map((layerItem) {
|
||||||
if (layerItem is BackgroundLayerData) {
|
if (layerItem is BackgroundLayerData) {
|
||||||
return BackgroundLayer(
|
return BackgroundLayer(
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
);
|
);
|
||||||
}
|
} else if (layerItem is ImageLayerData) {
|
||||||
|
|
||||||
// Image layer
|
|
||||||
if (layerItem is ImageLayerData) {
|
|
||||||
return ImageLayer(
|
return ImageLayer(
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return Container(); // Fallback, should not reach here
|
||||||
|
}),
|
||||||
|
|
||||||
// Emoji layer
|
// Draw layer (if needed, can be placed anywhere)
|
||||||
|
...layers.whereType<DrawLayerData>().map((layerItem) {
|
||||||
|
return DrawLayer(
|
||||||
|
layerData: layerItem,
|
||||||
|
onUpdate: onUpdate,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Emoji and Text layers at the top
|
||||||
|
...layers
|
||||||
|
.where((layerItem) =>
|
||||||
|
layerItem is EmojiLayerData || layerItem is TextLayerData)
|
||||||
|
.map((layerItem) {
|
||||||
if (layerItem is EmojiLayerData) {
|
if (layerItem is EmojiLayerData) {
|
||||||
return EmojiLayer(
|
return EmojiLayer(
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
);
|
);
|
||||||
}
|
} else if (layerItem is TextLayerData) {
|
||||||
|
|
||||||
// Text layer
|
|
||||||
if (layerItem is TextLayerData) {
|
|
||||||
return TextLayer(
|
return TextLayer(
|
||||||
layerData: layerItem,
|
layerData: layerItem,
|
||||||
onUpdate: onUpdate,
|
onUpdate: onUpdate,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return Container(); // Fallback, should not reach here
|
||||||
// Blank layer
|
}),
|
||||||
return Container();
|
],
|
||||||
}).toList(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:ui';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:camerawesome/camerawesome_plugin.dart';
|
import 'package:camerawesome/camerawesome_plugin.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
@ -58,6 +59,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MediaViewSizing(
|
return MediaViewSizing(
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(22),
|
borderRadius: BorderRadius.circular(22),
|
||||||
child: CameraAwesomeBuilder.custom(
|
child: CameraAwesomeBuilder.custom(
|
||||||
|
|
@ -86,8 +89,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
opaque: false,
|
opaque: false,
|
||||||
pageBuilder: (context, a1, a2) =>
|
pageBuilder: (context, a1, a2) =>
|
||||||
ShareImageEditorView(imageBytes: imageBytes),
|
ShareImageEditorView(imageBytes: imageBytes),
|
||||||
transitionsBuilder:
|
transitionsBuilder: (context, animation,
|
||||||
(context, animation, secondaryAnimation, child) {
|
secondaryAnimation, child) {
|
||||||
return child;
|
return child;
|
||||||
},
|
},
|
||||||
transitionDuration: Duration.zero,
|
transitionDuration: Duration.zero,
|
||||||
|
|
@ -102,7 +105,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
},
|
},
|
||||||
multiple: (multiple) {
|
multiple: (multiple) {
|
||||||
multiple.fileBySensor.forEach((key, value) {
|
multiple.fileBySensor.forEach((key, value) {
|
||||||
debugPrint('multiple image taken: $key ${value?.path}');
|
debugPrint(
|
||||||
|
'multiple image taken: $key ${value?.path}');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -117,7 +121,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
},
|
},
|
||||||
multiple: (multiple) {
|
multiple: (multiple) {
|
||||||
multiple.fileBySensor.forEach((key, value) {
|
multiple.fileBySensor.forEach((key, value) {
|
||||||
debugPrint('multiple video taken: $key ${value?.path}');
|
debugPrint(
|
||||||
|
'multiple video taken: $key ${value?.path}');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -172,7 +177,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
icon: FontAwesomeIcons.repeat,
|
icon: FontAwesomeIcons.repeat,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
cameraState.switchCameraSensor(
|
cameraState.switchCameraSensor(
|
||||||
aspectRatio: CameraAspectRatios.ratio_16_9);
|
aspectRatio:
|
||||||
|
CameraAspectRatios.ratio_16_9);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
SizedBox(height: 20),
|
||||||
|
|
@ -180,7 +186,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
icon: FontAwesomeIcons.bolt,
|
icon: FontAwesomeIcons.bolt,
|
||||||
color: isFlashOn
|
color: isFlashOn
|
||||||
? const Color.fromARGB(255, 255, 230, 0)
|
? const Color.fromARGB(255, 255, 230, 0)
|
||||||
: const Color.fromARGB(158, 255, 255, 255),
|
: const Color.fromARGB(
|
||||||
|
158, 255, 255, 255),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (isFlashOn) {
|
if (isFlashOn) {
|
||||||
cameraState.sensorConfig
|
cameraState.sensorConfig
|
||||||
|
|
@ -287,6 +294,20 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
// ),
|
// ),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (sharePreviewIsShown)
|
||||||
|
Positioned.fill(
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(
|
||||||
|
sigmaX: 100.0,
|
||||||
|
sigmaY: 100.0,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:twonly/src/components/image_editor/action_button.dart';
|
||||||
import 'package:twonly/src/components/media_view_sizing.dart';
|
import 'package:twonly/src/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/camera_to_share/share_image_view.dart';
|
import 'package:twonly/src/views/camera_to_share/share_image_view.dart';
|
||||||
|
|
@ -11,7 +12,6 @@ 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/modules/all_emojis.dart';
|
import 'package:twonly/src/components/image_editor/modules/all_emojis.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
import 'package:hand_signature/signature.dart';
|
|
||||||
|
|
||||||
List<Layer> layers = [];
|
List<Layer> layers = [];
|
||||||
List<Layer> undoLayers = [];
|
List<Layer> undoLayers = [];
|
||||||
|
|
@ -20,18 +20,13 @@ List<Layer> removedLayers = [];
|
||||||
class ShareImageEditorView extends StatefulWidget {
|
class ShareImageEditorView extends StatefulWidget {
|
||||||
const ShareImageEditorView({super.key, required this.imageBytes});
|
const ShareImageEditorView({super.key, required this.imageBytes});
|
||||||
final Uint8List imageBytes;
|
final Uint8List imageBytes;
|
||||||
static List<Shadow> get iconShadow => [
|
|
||||||
Shadow(
|
|
||||||
color: const Color.fromARGB(122, 0, 0, 0),
|
|
||||||
blurRadius: 5.0,
|
|
||||||
)
|
|
||||||
];
|
|
||||||
@override
|
@override
|
||||||
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ShareImageEditorView extends State<ShareImageEditorView> {
|
class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
bool _imageSaved = false;
|
bool _imageSaved = false;
|
||||||
|
bool _imageSaving = false;
|
||||||
|
|
||||||
ImageItem currentImage = ImageItem();
|
ImageItem currentImage = ImageItem();
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
|
@ -49,24 +44,90 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> get filterActions {
|
List<Widget> get actionsAtTheRight {
|
||||||
|
if (layers.isNotEmpty &&
|
||||||
|
layers.last.isEditing &&
|
||||||
|
layers.last.hasCustomActionButtons) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return <Widget>[
|
||||||
|
BottomButton(
|
||||||
|
icon: FontAwesomeIcons.font,
|
||||||
|
onTap: () async {
|
||||||
|
undoLayers.clear();
|
||||||
|
removedLayers.clear();
|
||||||
|
layers.add(TextLayerData());
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BottomButton(
|
||||||
|
icon: FontAwesomeIcons.pencil,
|
||||||
|
onTap: () async {
|
||||||
|
// var drawing = await Navigator.push(
|
||||||
|
// context,
|
||||||
|
// PageRouteBuilder(
|
||||||
|
// opaque: false,
|
||||||
|
// pageBuilder: (context, a, b) => ImageEditorDrawing(
|
||||||
|
// image: currentImage,
|
||||||
|
// ),
|
||||||
|
// transitionDuration: Duration.zero,
|
||||||
|
// reverseTransitionDuration: Duration.zero,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// if (drawing != null) {
|
||||||
|
undoLayers.clear();
|
||||||
|
removedLayers.clear();
|
||||||
|
|
||||||
|
layers.add(DrawLayerData());
|
||||||
|
|
||||||
|
// setState(() {});
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BottomButton(
|
||||||
|
icon: FontAwesomeIcons.faceGrinWide,
|
||||||
|
onTap: () async {
|
||||||
|
EmojiLayerData? layer = await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return const Emojis();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (layer == null) return;
|
||||||
|
|
||||||
|
undoLayers.clear();
|
||||||
|
removedLayers.clear();
|
||||||
|
layers.add(layer);
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> get actionsAtTheTop {
|
||||||
|
if (layers.isNotEmpty &&
|
||||||
|
layers.last.isEditing &&
|
||||||
|
layers.last.hasCustomActionButtons) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
IconButton(
|
ActionButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.xmark,
|
FontAwesomeIcons.xmark,
|
||||||
size: 30, shadows: ShareImageEditorView.iconShadow),
|
|
||||||
color: Colors.white,
|
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Expanded(child: Container()),
|
Expanded(child: Container()),
|
||||||
IconButton(
|
const SizedBox(width: 8),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
ActionButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.rotateLeft,
|
FontAwesomeIcons.rotateLeft,
|
||||||
color: layers.length > 1 || removedLayers.isNotEmpty
|
color: layers.length > 1 || removedLayers.isNotEmpty
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Colors.grey,
|
: Colors.grey,
|
||||||
shadows: ShareImageEditorView.iconShadow),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (removedLayers.isNotEmpty) {
|
if (removedLayers.isNotEmpty) {
|
||||||
layers.add(removedLayers.removeLast());
|
layers.add(removedLayers.removeLast());
|
||||||
|
|
@ -79,16 +140,13 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
const SizedBox(width: 8),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
ActionButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.rotateRight,
|
FontAwesomeIcons.rotateRight,
|
||||||
color: undoLayers.isNotEmpty ? Colors.white : Colors.grey,
|
color: undoLayers.isNotEmpty ? Colors.white : Colors.grey,
|
||||||
shadows: ShareImageEditorView.iconShadow),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (undoLayers.isEmpty) return;
|
if (undoLayers.isEmpty) return;
|
||||||
|
|
||||||
layers.add(undoLayers.removeLast());
|
layers.add(undoLayers.removeLast());
|
||||||
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -168,7 +226,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
right: 0,
|
right: 0,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: filterActions,
|
children: actionsAtTheTop,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -181,69 +239,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: actionsAtTheRight,
|
||||||
BottomButton(
|
|
||||||
icon: FontAwesomeIcons.font,
|
|
||||||
onTap: () async {
|
|
||||||
undoLayers.clear();
|
|
||||||
removedLayers.clear();
|
|
||||||
layers.add(TextLayerData());
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
BottomButton(
|
|
||||||
icon: FontAwesomeIcons.pencil,
|
|
||||||
onTap: () async {
|
|
||||||
var drawing = await Navigator.push(
|
|
||||||
context,
|
|
||||||
PageRouteBuilder(
|
|
||||||
opaque: false,
|
|
||||||
pageBuilder: (context, a, b) => ImageEditorDrawing(
|
|
||||||
image: currentImage,
|
|
||||||
),
|
|
||||||
transitionDuration: Duration.zero,
|
|
||||||
reverseTransitionDuration: Duration.zero,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (drawing != null) {
|
|
||||||
undoLayers.clear();
|
|
||||||
removedLayers.clear();
|
|
||||||
|
|
||||||
layers.add(
|
|
||||||
ImageLayerData(
|
|
||||||
image: ImageItem(drawing),
|
|
||||||
offset: Offset(0, 0),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SizedBox(height: 20),
|
|
||||||
BottomButton(
|
|
||||||
icon: FontAwesomeIcons.faceGrinWide,
|
|
||||||
onTap: () async {
|
|
||||||
EmojiLayerData? layer = await showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
backgroundColor: Colors.black,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return const Emojis();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (layer == null) return;
|
|
||||||
|
|
||||||
undoLayers.clear();
|
|
||||||
removedLayers.clear();
|
|
||||||
layers.add(layer);
|
|
||||||
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -259,7 +255,12 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
icon: _imageSaved
|
icon: _imageSaving
|
||||||
|
? SizedBox(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 1))
|
||||||
|
: _imageSaved
|
||||||
? Icon(Icons.check)
|
? Icon(Icons.check)
|
||||||
: FaIcon(FontAwesomeIcons.floppyDisk),
|
: FaIcon(FontAwesomeIcons.floppyDisk),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
|
|
@ -271,11 +272,15 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
: Theme.of(context).colorScheme.primary,
|
: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
setState(() {
|
||||||
|
_imageSaving = true;
|
||||||
|
});
|
||||||
Uint8List? imageBytes = await getMergedImage();
|
Uint8List? imageBytes = await getMergedImage();
|
||||||
if (imageBytes == null || !context.mounted) return;
|
if (imageBytes == null || !context.mounted) return;
|
||||||
final res = await saveImageToGallery(imageBytes);
|
final res = await saveImageToGallery(imageBytes);
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
_imageSaving = false;
|
||||||
_imageSaved = true;
|
_imageSaved = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -341,10 +346,10 @@ class BottomButton extends StatelessWidget {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
FaIcon(
|
ActionButton(
|
||||||
icon,
|
icon,
|
||||||
color: color,
|
color: color,
|
||||||
shadows: ShareImageEditorView.iconShadow,
|
onPressed: onTap ?? () {},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
],
|
],
|
||||||
|
|
@ -354,240 +359,20 @@ 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;
|
||||||
|
|
||||||
const ImageEditorDrawing({
|
// const ImageEditorDrawing({
|
||||||
super.key,
|
// super.key,
|
||||||
required this.image,
|
// required this.image,
|
||||||
});
|
// });
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
State<ImageEditorDrawing> createState() => _ImageEditorDrawingState();
|
// State<ImageEditorDrawing> createState() => _ImageEditorDrawingState();
|
||||||
}
|
// }
|
||||||
|
|
||||||
class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
|
// class _ImageEditorDrawingState extends State<ImageEditorDrawing> {
|
||||||
Color pickerColor = Colors.white, currentColor = Colors.white;
|
|
||||||
|
|
||||||
var screenshotController = ScreenshotController();
|
// }
|
||||||
|
// }
|
||||||
final control = HandSignatureControl(
|
|
||||||
threshold: 3.0,
|
|
||||||
smoothRatio: 0.65,
|
|
||||||
velocityRange: 2.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
List<CubicPath> undoList = [];
|
|
||||||
bool skipNextEvent = false;
|
|
||||||
|
|
||||||
// void changeColor(Colors color) {
|
|
||||||
// currentColor = color.color;
|
|
||||||
// currentBackgroundColor = color.background;
|
|
||||||
|
|
||||||
// setState(() {});
|
|
||||||
// }
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
control.addListener(() {
|
|
||||||
if (control.hasActivePath) return;
|
|
||||||
|
|
||||||
if (skipNextEvent) {
|
|
||||||
skipNextEvent = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
undoList = [];
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final colors = [
|
|
||||||
Colors.black,
|
|
||||||
Colors.white,
|
|
||||||
Colors.blue,
|
|
||||||
Colors.green,
|
|
||||||
Colors.pink,
|
|
||||||
Colors.purple,
|
|
||||||
Colors.brown,
|
|
||||||
Colors.indigo,
|
|
||||||
];
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
backgroundColor: Colors.red.withAlpha(0),
|
|
||||||
body: SafeArea(
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
Positioned.fill(
|
|
||||||
top: 0,
|
|
||||||
child: Container(
|
|
||||||
height: 600,
|
|
||||||
width: 300,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color.fromARGB(0, 210, 7, 7),
|
|
||||||
),
|
|
||||||
// child: Container(),
|
|
||||||
child: Screenshot(
|
|
||||||
controller: screenshotController,
|
|
||||||
// image: widget.options.showBackground
|
|
||||||
// ? DecorationImage(
|
|
||||||
// image: Image.memory(widget.image.bytes).image,
|
|
||||||
// fit: BoxFit.contain,
|
|
||||||
// )
|
|
||||||
// : null,
|
|
||||||
// child: Container(),
|
|
||||||
child: HandSignature(
|
|
||||||
control: control,
|
|
||||||
color: currentColor,
|
|
||||||
width: 1.0,
|
|
||||||
maxWidth: 7.0,
|
|
||||||
type: SignatureDrawType.shape,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: 100,
|
|
||||||
right: 50,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
icon: const Icon(Icons.clear),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
icon: Icon(
|
|
||||||
Icons.undo,
|
|
||||||
color: control.paths.isNotEmpty
|
|
||||||
? Colors.white
|
|
||||||
: Colors.white.withAlpha(80),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
if (control.paths.isEmpty) return;
|
|
||||||
skipNextEvent = true;
|
|
||||||
undoList.add(control.paths.last);
|
|
||||||
control.stepBack();
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
icon: Icon(
|
|
||||||
Icons.redo,
|
|
||||||
color: undoList.isNotEmpty
|
|
||||||
? Colors.white
|
|
||||||
: Colors.white.withAlpha(80),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
if (undoList.isEmpty) return;
|
|
||||||
|
|
||||||
control.paths.add(undoList.removeLast());
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
icon: const Icon(Icons.check),
|
|
||||||
onPressed: () async {
|
|
||||||
if (control.paths.isEmpty) return Navigator.pop(context);
|
|
||||||
|
|
||||||
var data = await control.toImage(
|
|
||||||
color: currentColor,
|
|
||||||
height: widget.image.height,
|
|
||||||
width: widget.image.width,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
return Navigator.pop(context, data!.buffer.asUint8List());
|
|
||||||
|
|
||||||
// var loadingScreen = showLoadingScreen(context);
|
|
||||||
// var image = await screenshotController.capture();
|
|
||||||
// loadingScreen.hide();
|
|
||||||
|
|
||||||
// if (!mounted) return;
|
|
||||||
|
|
||||||
// return Navigator.pop(context, image);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: 0,
|
|
||||||
top: 50,
|
|
||||||
child: Container(
|
|
||||||
child: Container(
|
|
||||||
// height: 80,
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: const Color.fromARGB(130, 0, 0, 0),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
for (var color in colors)
|
|
||||||
ColorButton(
|
|
||||||
color: color,
|
|
||||||
onTap: (color) {
|
|
||||||
currentColor = color;
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
isSelected: color == currentColor,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ColorButton extends StatelessWidget {
|
|
||||||
final Color color;
|
|
||||||
final Function(Color) onTap;
|
|
||||||
final bool isSelected;
|
|
||||||
|
|
||||||
const ColorButton({
|
|
||||||
super.key,
|
|
||||||
required this.color,
|
|
||||||
required this.onTap,
|
|
||||||
this.isSelected = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
onTap(color);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 17,
|
|
||||||
width: 17,
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(
|
|
||||||
color: isSelected ? Colors.white : Colors.white54,
|
|
||||||
width: isSelected ? 2 : 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ environment:
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
camerawesome: ^2.1.0
|
camerawesome: ^2.1.0
|
||||||
|
# camerawesome:
|
||||||
|
# path: ../CamerAwesome
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
connectivity_plus: ^6.1.2
|
connectivity_plus: ^6.1.2
|
||||||
cv: ^1.1.3
|
cv: ^1.1.3
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue