From 655ccdfd6399b9f321956bfefa4c7acd276599a1 Mon Sep 17 00:00:00 2001 From: Naser Elziadna Date: Sun, 22 Dec 2024 19:53:22 +0200 Subject: [PATCH] changes --- .../providers/canvas/canvas_provider.dart | 2 +- .../effects/normal_with_shader_effect.dart | 78 +++++++++++----- .../settings/brush_settings_config.dart | 12 ++- lib/domain/models/effects/text_effect.dart | 5 +- lib/main.dart | 4 +- .../brush_settings/brush_settings_panel.dart | 11 ++- .../widgets/dialogs/canvas_size_dialog.dart | 90 +++++++++++++++++++ lib/presentation/common/widgets/popover.dart | 68 +++++++++----- .../common/widgets/tools/tools_widget.dart | 65 ++++++-------- .../painting/brush_preview_painter.dart | 83 +++++++++++++---- .../painting/last_image_as_background.dart | 13 +-- .../about_me_screen/about_me_page.dart | 32 +++---- .../screens/canvas_screen/canvas_screen.dart | 5 +- 13 files changed, 335 insertions(+), 133 deletions(-) create mode 100644 lib/presentation/common/widgets/dialogs/canvas_size_dialog.dart diff --git a/lib/application/providers/canvas/canvas_provider.dart b/lib/application/providers/canvas/canvas_provider.dart index 44a5c2a..4ee213a 100644 --- a/lib/application/providers/canvas/canvas_provider.dart +++ b/lib/application/providers/canvas/canvas_provider.dart @@ -138,7 +138,7 @@ class CanvasNotifier extends _$CanvasNotifier { Future canvasToImage(GlobalKey globalKey) async { final boundary = globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary; - return await boundary.toImage(); + return await boundary.toImage(pixelRatio:5); } Future saveToGallery(GlobalKey globalKey) async { diff --git a/lib/domain/models/effects/normal_with_shader_effect.dart b/lib/domain/models/effects/normal_with_shader_effect.dart index dd913c3..9e86918 100644 --- a/lib/domain/models/effects/normal_with_shader_effect.dart +++ b/lib/domain/models/effects/normal_with_shader_effect.dart @@ -1,33 +1,65 @@ +import 'dart:ui'; import 'package:doddle/domain/models/effects/pen_effect.dart'; +import 'package:doddle/domain/models/point.dart'; import 'package:flutter/material.dart'; +class RainbowStroke { + final Offset position; + final Color color; + final double size; + + RainbowStroke({ + required this.position, + required this.color, + required this.size, + }); +} + class NormalWithShaderEffect extends PenEffect { + final List strokes = []; + int colorIndex = 0; + + static const List rainbowColors = [ + Color.fromARGB(255, 255, 0, 0), // Red + Color.fromARGB(255, 255, 127, 0), // Orange + Color.fromARGB(255, 255, 255, 0), // Yellow + Color.fromARGB(255, 0, 255, 0), // Green + Color.fromARGB(255, 0, 0, 255), // Blue + Color.fromARGB(255, 75, 0, 130), // Indigo + Color.fromARGB(255, 148, 0, 211), // Violet + ]; + @override void paint(Canvas canvas, Path path, Paint paint) { - canvas.drawPath( - path, - Paint() - ..shader = sweepShader - ..strokeJoin = StrokeJoin.round - ..style = PaintingStyle.stroke - ..strokeWidth = drawController.penSize!, - ); + for (var stroke in strokes) { + final positions = getSymmetricalPositions(stroke.position); + canvas.drawPoints(PointMode.lines, positions, + Paint() + ..color = stroke.color + ..style = PaintingStyle.stroke + ..strokeWidth = stroke.size + ..strokeCap = StrokeCap.round + ); + } } - static final SweepGradient colorWheelGradient = SweepGradient( - center: Alignment.bottomRight, - colors: [ - Color.fromARGB(255, 255, 0, 0), - Color.fromARGB(255, 255, 255, 0), - Color.fromARGB(255, 0, 255, 0), - Color.fromARGB(255, 0, 255, 255), - Color.fromARGB(255, 0, 0, 255), - Color.fromARGB(255, 255, 0, 255), - Color.fromARGB(255, 255, 0, 0), -]); -// If we create a shader from the above SweepGraident, we get -// a crash on web, but only on web. - static final Shader sweepShader = - colorWheelGradient.createShader(const Rect.fromLTWH(0, 0, 100, 10)); + @override + void onPointAdd(Point point) { + if (point.offset == null) return; + + strokes.add(RainbowStroke( + position: point.offset!, + color: rainbowColors[colorIndex], + size: drawController.penSize ?? 2.0, + )); + + colorIndex = (colorIndex + 1) % rainbowColors.length; + } + + @override + void onPointEnd() { + colorIndex = 0; + strokes.clear(); + } } diff --git a/lib/domain/models/effects/settings/brush_settings_config.dart b/lib/domain/models/effects/settings/brush_settings_config.dart index 57ad7e9..44f4106 100644 --- a/lib/domain/models/effects/settings/brush_settings_config.dart +++ b/lib/domain/models/effects/settings/brush_settings_config.dart @@ -171,14 +171,22 @@ class BrushConfigs { maxValue: 72.0, icon: Icons.format_size, ), - 'spacing': BrushSettingConfig( - label: 'Spacing', + 'wordSpacing': BrushSettingConfig( + label: 'Word Spacing', type: SettingType.slider, defaultValue: 50.0, minValue: 10.0, maxValue: 200.0, icon: Icons.space_bar, ), + 'letterSpacing': BrushSettingConfig( + label: 'Letter Spacing', + type: SettingType.slider, + defaultValue: 0.0, + minValue: -50.0, + maxValue: 50.0, + icon: Icons.space_bar, + ), 'randomRotation': BrushSettingConfig( label: 'Random Rotation', type: SettingType.toggle, diff --git a/lib/domain/models/effects/text_effect.dart b/lib/domain/models/effects/text_effect.dart index fdef7df..bcc29e6 100644 --- a/lib/domain/models/effects/text_effect.dart +++ b/lib/domain/models/effects/text_effect.dart @@ -13,7 +13,8 @@ class TextEffect extends PenEffect { String get text => settings.getValue('text') ?? 'Hello'; double get fontSize => settings.getValue('fontSize') ?? 20.0; bool get randomRotation => settings.getValue('randomRotation') ?? false; - double get spacing => settings.getValue('spacing') ?? 50.0; + double get wordSpacing => settings.getValue('wordSpacing') ?? 0.0; + double get letterSpacing => settings.getValue('letterSpacing') ?? 0.0; @override void paint(Canvas canvas, Path path, Paint paint) { @@ -25,6 +26,8 @@ class TextEffect extends PenEffect { style: TextStyle( color: drawController.currentColor, fontSize: fontSize, + wordSpacing: wordSpacing, + letterSpacing: letterSpacing, ), ), textDirection: TextDirection.ltr, diff --git a/lib/main.dart b/lib/main.dart index 4a0df0b..9be031c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:doddle/core/theme/app_theme.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sizer/sizer.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -23,7 +24,8 @@ Future initializeApp() async { void main() async { await initializeApp(); - + //dont allow rotation + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); runApp( const ProviderScope( child: MyApp(), diff --git a/lib/presentation/common/widgets/brush_settings/brush_settings_panel.dart b/lib/presentation/common/widgets/brush_settings/brush_settings_panel.dart index 30ba64b..5502374 100644 --- a/lib/presentation/common/widgets/brush_settings/brush_settings_panel.dart +++ b/lib/presentation/common/widgets/brush_settings/brush_settings_panel.dart @@ -117,6 +117,13 @@ class BrushSettingsPanel extends ConsumerWidget { ), ); case SettingType.text: + // Create a persistent TextEditingController to maintain state + final controller = TextEditingController(text: currentValue); + // Maintain cursor position + controller.selection = TextSelection.fromPosition( + TextPosition(offset: controller.text.length) + ); + return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Column( @@ -133,7 +140,7 @@ class BrushSettingsPanel extends ConsumerWidget { ), const SizedBox(height: 8), TextField( - controller: TextEditingController(text: currentValue), + controller: controller, decoration: InputDecoration( border: OutlineInputBorder(), contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), @@ -155,7 +162,7 @@ class BrushSettingsPanel extends ConsumerWidget { return Center( child: TextButton.icon( icon: const Icon(Icons.restart_alt), - label: const Text('Reset to Default'), + label: const Text('Reset Brush Settings to Default'), onPressed: () { ref.read(brushSettingsProvider(penTool).notifier).resetToDefault(); }, diff --git a/lib/presentation/common/widgets/dialogs/canvas_size_dialog.dart b/lib/presentation/common/widgets/dialogs/canvas_size_dialog.dart new file mode 100644 index 0000000..313bf59 --- /dev/null +++ b/lib/presentation/common/widgets/dialogs/canvas_size_dialog.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; + +class CanvasSize { + final String name; + final Size size; + + const CanvasSize({ + required this.name, + required this.size, + }); +} + +class CanvasSizeDialog extends StatelessWidget { + CanvasSizeDialog({Key? key}) : super(key: key); + + final List canvasSizes = [ + CanvasSize(name: 'Small (1080x1080)', size: const Size(1080, 1080)), + CanvasSize(name: 'Medium (1920x1080)', size: const Size(1920, 1080)), + CanvasSize(name: 'Large (2560x1440)', size: const Size(2560, 1440)), + CanvasSize(name: 'Custom', size: Size.zero), + ]; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Select Canvas Size'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: canvasSizes.map((size) { + return ListTile( + title: Text(size.name), + onTap: () { + if (size.name == 'Custom') { + _showCustomSizeDialog(context); + } else { + Navigator.of(context).pop(size.size); + } + }, + ); + }).toList(), + ), + ), + ); + } + + void _showCustomSizeDialog(BuildContext context) { + final widthController = TextEditingController(); + final heightController = TextEditingController(); + + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Custom Size'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: widthController, + decoration: const InputDecoration(labelText: 'Width'), + keyboardType: TextInputType.number, + ), + TextField( + controller: heightController, + decoration: const InputDecoration(labelText: 'Height'), + keyboardType: TextInputType.number, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + final width = double.tryParse(widthController.text); + final height = double.tryParse(heightController.text); + if (width != null && height != null) { + Navigator.of(context).pop(); + Navigator.of(context).pop(Size(width, height)); + } + }, + child: const Text('OK'), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/common/widgets/popover.dart b/lib/presentation/common/widgets/popover.dart index 9bf159c..f16284f 100644 --- a/lib/presentation/common/widgets/popover.dart +++ b/lib/presentation/common/widgets/popover.dart @@ -1,13 +1,47 @@ import 'package:flutter/material.dart'; +import 'package:sizer/sizer.dart'; -class Popover extends StatelessWidget { +class Popover extends StatefulWidget { const Popover({ Key? key, this.child, - }) : super(key: key); + }): super(key: key); final Widget? child; + @override + State createState() => _PopoverState(); +} + +class _PopoverState extends State with SingleTickerProviderStateMixin { + Widget _buildHandle(BuildContext context) { + final theme = Theme.of(context); + + return SizedBox( + width: double.infinity, + child: Stack( + alignment: Alignment.topCenter, + children: [ + FractionallySizedBox( + widthFactor: 0.25, + child: Container( + margin: const EdgeInsets.symmetric( + vertical: 12.0, + ), + child: Container( + height: 5.0, + decoration: BoxDecoration( + color: theme.dividerColor, + borderRadius: const BorderRadius.all(Radius.circular(2.5)), + ), + ), + ), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -15,33 +49,23 @@ class Popover extends StatelessWidget { return Container( margin: const EdgeInsets.all(16.0), clipBehavior: Clip.antiAlias, + constraints: BoxConstraints( + maxWidth: 90.w, + ), decoration: BoxDecoration( color: theme.cardColor, borderRadius: const BorderRadius.all(Radius.circular(16.0)), ), child: Column( mainAxisSize: MainAxisSize.min, - children: [_buildHandle(context), if (child != null) child!], - ), - ); - } - - Widget _buildHandle(BuildContext context) { - final theme = Theme.of(context); - - return FractionallySizedBox( - widthFactor: 0.25, - child: Container( - margin: const EdgeInsets.symmetric( - vertical: 12.0, - ), - child: Container( - height: 5.0, - decoration: BoxDecoration( - color: theme.dividerColor, - borderRadius: const BorderRadius.all(Radius.circular(2.5)), + children: [ + _buildHandle(context), + Flexible( + child: SingleChildScrollView( + child: widget.child, + ), ), - ), + ], ), ); } diff --git a/lib/presentation/common/widgets/tools/tools_widget.dart b/lib/presentation/common/widgets/tools/tools_widget.dart index 15a8c40..ce4974c 100644 --- a/lib/presentation/common/widgets/tools/tools_widget.dart +++ b/lib/presentation/common/widgets/tools/tools_widget.dart @@ -19,9 +19,9 @@ class ToolsWidget extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return Container( height: MediaQuery.of(context).size.height * .1, - color: Colors.purple[800], + color: Colors.purple[600], child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildToolButton( context, @@ -41,16 +41,20 @@ class ToolsWidget extends ConsumerWidget { orElse: () => symmetryLines.first, ); - return _buildToolButton( - context, - icon: Stack( - alignment: AlignmentDirectional.center, - children: [ - Assets.svg.symmetricalLineBg.svg(width: 80), - selectedLine.picture, - ], + return SizedBox( + width: 80, + height: 80, + child: _buildToolButton( + context, + icon: Stack( + alignment: AlignmentDirectional.center, + children: [ + Assets.svg.symmetricalLineBg.svg(width: 80), + selectedLine.picture, + ], + ), + toolType: ToolType.symmyrticllLine, ), - toolType: ToolType.symmyrticllLine, ); } ), @@ -69,7 +73,7 @@ class ToolsWidget extends ConsumerWidget { context, icon: const Icon( Icons.settings, - size: 30, + size: 80, color: Colors.white, ), toolType: ToolType.canvasSettings, @@ -86,11 +90,7 @@ class ToolsWidget extends ConsumerWidget { }) { return GestureDetector( onTap: () { - if (toolType == ToolType.brushs) { - _showBrushSettings(context); - } else { - _showToolSettings(context, toolType); - } + _showToolSettings(context, toolType); }, child: icon, ); @@ -107,8 +107,9 @@ class ToolsWidget extends ConsumerWidget { void _showToolSettings(BuildContext context, ToolType toolType) { showModalBottomSheet( - backgroundColor: Colors.transparent, context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, builder: (context) { return Popover( child: _buildToolContent(toolType), @@ -117,24 +118,6 @@ class ToolsWidget extends ConsumerWidget { ); } - void _showBrushSettings(BuildContext context) { - showModalBottomSheet( - context: context, - builder: (BuildContext context) { - return const SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - BrushToolGrid(), - Divider(), - BrushSettingsPanel(), - ], - ), - ); - }, - ); - } - Widget _buildToolContent(ToolType toolType) { switch (toolType) { case ToolType.brushs: @@ -144,7 +127,15 @@ class ToolsWidget extends ConsumerWidget { case ToolType.symmyrticllLine: return const SymmetryToolGrid(); case ToolType.canvasSettings: - return const CanvasSettingsToolGrid(); + return SingleChildScrollView( + child: Column( + children: [ + CanvasSettingsToolGrid(), + Divider(), + BrushSettingsPanel(), + ], + ), + ); } } } \ No newline at end of file diff --git a/lib/presentation/painting/brush_preview_painter.dart b/lib/presentation/painting/brush_preview_painter.dart index 8cb233e..5bfa6b5 100644 --- a/lib/presentation/painting/brush_preview_painter.dart +++ b/lib/presentation/painting/brush_preview_painter.dart @@ -1,3 +1,4 @@ +import 'package:doddle/application/providers/brush_settings_provider.dart'; import 'package:doddle/application/providers/canvas/canvas_provider.dart'; import 'package:doddle/domain/models/effects/pen_effect.dart'; import 'package:flutter/material.dart'; @@ -17,32 +18,76 @@ class BrushPreviewPainter extends CustomPainter { final center = Offset(size.width / 2, size.height / 2); canvas.translate(center.dx, center.dy); - // Create a wavy line for preview - final points = _generatePreviewPoints(size); - final controller = DrawController( - points: points, - penTool: ref.read(canvasNotifierProvider).penTool, - penSize: ref.read(canvasNotifierProvider).penSize, - currentColor: ref.read(canvasNotifierProvider).currentColor, - effects: ref.read(canvasNotifierProvider).effects, - ); - - final effect = controller.effects[controller.penTool]; + final currentPenTool = ref.read(canvasNotifierProvider).penTool; - if (effect != null) { - Path path = Path(); - Paint paint = Paint() - ..strokeCap = StrokeCap.round - ..style = PaintingStyle.stroke - ..strokeWidth = 2.0; + // Special handling for text brush preview + if (currentPenTool == PenTool.textPen) { + _drawTextPreview(canvas, size); + } else { + // Original wave preview for other brushes + final points = _generatePreviewPoints(size); + final controller = DrawController( + points: points, + penTool: currentPenTool, + penSize: ref.read(canvasNotifierProvider).penSize, + currentColor: ref.read(canvasNotifierProvider).currentColor, + effects: ref.read(canvasNotifierProvider).effects, + ); - _drawPreviewPoints(canvas, path, paint, effect, points); - effect.paint(canvas, path, paint); + final effect = controller.effects[controller.penTool]; + + if (effect != null) { + Path path = Path(); + Paint paint = Paint() + ..strokeCap = StrokeCap.round + ..style = PaintingStyle.stroke + ..strokeWidth = controller.penSize ?? 2.0; + + _drawPreviewPoints(canvas, path, paint, effect, points); + effect.paint(canvas, path, paint); + } } canvas.restore(); } + void _drawTextPreview(Canvas canvas, Size size) { + final settings = ref.read(brushSettingsProvider(PenTool.textPen)); + final text = settings.getValue('text') ?? 'Hello'; + final fontSize = settings.getValue('fontSize') ?? 20.0; + final color = ref.read(canvasNotifierProvider).currentColor; + + final textPainter = TextPainter( + text: TextSpan( + text: text, + style: TextStyle( + color: color, + fontSize: fontSize, + ), + ), + textDirection: TextDirection.ltr, + ); + textPainter.layout(); + + // Draw multiple instances of text along a curved path + for (double x = -size.width/3; x <= size.width/3; x += 50) { + final y = math.sin(x * 0.1) * 20; + + canvas.save(); + canvas.translate(x, y); + + // Add slight rotation for visual interest + canvas.rotate(math.sin(x * 0.05) * 0.3); + + textPainter.paint( + canvas, + Offset(-textPainter.width / 2, -textPainter.height / 2), + ); + + canvas.restore(); + } + } + List _generatePreviewPoints(Size size) { final points = []; final width = size.width * 0.4; diff --git a/lib/presentation/painting/last_image_as_background.dart b/lib/presentation/painting/last_image_as_background.dart index 58f2c67..592d9ab 100644 --- a/lib/presentation/painting/last_image_as_background.dart +++ b/lib/presentation/painting/last_image_as_background.dart @@ -9,11 +9,14 @@ class LastImageAsBackground extends CustomPainter { @override void paint(Canvas canvas, Size size) { if (image != null) { - canvas.drawImage(image!, Offset.zero, ui.Paint() - ..blendMode = BlendMode.srcIn - ..filterQuality = ui.FilterQuality.high - // ..isAntiAlias = true, - ); + paintImage( + canvas: canvas, + rect: Offset.zero & size, + image: image!, + fit: BoxFit.cover, + filterQuality: FilterQuality.high, + isAntiAlias: true, + ); } } diff --git a/lib/presentation/screens/about_me_screen/about_me_page.dart b/lib/presentation/screens/about_me_screen/about_me_page.dart index a031435..c74b2cf 100644 --- a/lib/presentation/screens/about_me_screen/about_me_page.dart +++ b/lib/presentation/screens/about_me_screen/about_me_page.dart @@ -7,23 +7,23 @@ class AboutMeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - body: Padding( - padding: const EdgeInsets.symmetric(vertical: 100), + body:Padding( + padding: const EdgeInsets.all(8.0), child: ContactUs( - logo: const AssetImage("assets/NaserElziadna.jpg"), - email: 'elzianda10@gmail.com', - companyName: 'Naser Elzianda', - phoneNumber: '+972584029927', - dividerThickness: 2, - website: 'https://www.nmmsoft.com', - githubUserName: 'NaserElziadna', - linkedinURL: 'https://www.linkedin.com/in/naser-hassan-b452411a1/', - tagLine: 'Full Stack Developer', - cardColor: Colors.white, - companyColor: Colors.red, - taglineColor: Colors.green, - textColor: Colors.black, - ), + logo: const AssetImage("assets/NaserElziadna.jpg"), + email: 'elzianda10@gmail.com', + companyName: 'Naser Elzianda', + phoneNumber: '+972584029927', + dividerThickness: 2, + website: 'https://www.nmmsoft.com', + githubUserName: 'NaserElziadna', + linkedinURL: 'https://www.linkedin.com/in/naser-hassan-b452411a1/', + tagLine: 'Full Stack Developer', + cardColor: Colors.white, + companyColor: Colors.red, + taglineColor: Colors.green, + textColor: Colors.black, + ), ), ); } diff --git a/lib/presentation/screens/canvas_screen/canvas_screen.dart b/lib/presentation/screens/canvas_screen/canvas_screen.dart index f62ae40..c0bae92 100644 --- a/lib/presentation/screens/canvas_screen/canvas_screen.dart +++ b/lib/presentation/screens/canvas_screen/canvas_screen.dart @@ -300,10 +300,7 @@ class _CanvasScreenState extends ConsumerState { ? null : drawController.stamp?.last?.image, ), - size: Size( - kCanvasSize.width / 2, - kCanvasSize.height / 2, - ), + size: kCanvasSize, willChange: true, isComplex: true, child: const SizedBox.expand(),