stack_board_plus 0.0.1 copy "stack_board_plus: ^0.0.1" to clipboard
stack_board_plus: ^0.0.1 copied to clipboard

An advanced, stable tool for building photo editing and canvas-based apps with layers, transformations, and interactive elements.

example/lib/main.dart

import 'dart:convert';
import 'dart:io';
import 'dart:math';

import 'package:example/common/constants.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:image_picker/image_picker.dart';
import 'package:stack_board_plus/flutter_stack_board_plus.dart';
import 'package:stack_board_plus/stack_board_plus_item.dart';
import 'package:stack_board_plus/stack_case.dart';
import 'package:stack_board_plus/stack_items.dart';

class ColorContent extends StackItemContent {
  ColorContent({required this.color});

  Color color;

  @override
  Map<String, dynamic> toJson() {
    return <String, dynamic>{
      'color': color.value,
    };
  }
}

class ColorStackItem extends StackItem<ColorContent> {
  ColorStackItem({
    required Size size,
    String? id,
    Offset? offset,
    double? angle,
    StackItemStatus? status,
    ColorContent? content,
  }) : super(
          id: id,
          size: size,
          offset: offset,
          angle: angle,
          status: status,
          content: content,
          lockZOrder: true,
        );

  @override
  ColorStackItem copyWith({
    Size? size,
    Offset? offset,
    double? angle,
    StackItemStatus? status,
    bool? lockZOrder,
    ColorContent? content,
  }) {
    return ColorStackItem(
      id: id, // <= must !!
      size: size ?? this.size,
      offset: offset ?? this.offset,
      angle: angle ?? this.angle,
      status: status ?? this.status,
      content: content ?? this.content,
    );
  }
}

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomePage());
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late StackBoardPlusController _boardController;
  
  // Background customization variables
  Color _backgroundColor = Colors.white;
  Gradient? _backgroundGradient = const LinearGradient(
    colors: [Colors.grey, Colors.white],
    begin: Alignment.topLeft,
    end: Alignment.bottomRight,
  );
  File? _backgroundImage;
  double _backgroundWidth = 800.0;
  double _backgroundHeight = 600.0;
  BoxFit _backgroundFit = BoxFit.cover;
  bool _useGradient = true;  // Start with gradient as default
  bool _useImage = false;

  @override
  void initState() {
    super.initState();
    _boardController = StackBoardPlusController();
  }

  @override
  void dispose() {
    _boardController.dispose();
    super.dispose();
  }

  /// Delete intercept
  Future<void> _onDel(StackItem<StackItemContent> item) async {
    final bool? r = await showDialog<bool>(
      context: context,
      barrierDismissible: false,
      builder: (_) {
        return AlertDialog(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(20),
          ),
          elevation: 10,
          title: Row(
            children: [
              Icon(Icons.warning_amber_rounded, color: Colors.orange[600], size: 28),
              const SizedBox(width: 12),
              const Text(
                'Delete Item',
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
          content: const Text(
            'Are you sure you want to delete this item? This action cannot be undone.',
            style: TextStyle(fontSize: 16),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context, false),
              style: TextButton.styleFrom(
                padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
              ),
              child: const Text(
                'Cancel',
                style: TextStyle(fontSize: 16, color: Colors.grey),
              ),
            ),
            ElevatedButton(
              onPressed: () => Navigator.pop(context, true),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.red,
                foregroundColor: Colors.white,
                padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8),
                ),
              ),
              child: const Text(
                'Delete',
                style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
              ),
            ),
          ],
        );
      },
    );

    if (r == true) {
      _boardController.removeById(item.id);
    }
  }

  /// Add text item
  void _addTextItem() {
    _boardController.addItem(
      StackTextItem(
        size: const Size(200, 100),
        content: TextItemContent(data: 'Sample Text', ),
      ),
    );
  }

  /// Add custom item
  void _addCustomItem() {
    final Color color =
        Colors.primaries[Random().nextInt(Colors.primaries.length)];
    _boardController.addItem(
      ColorStackItem(
        size: const Size.square(100),
        content: ColorContent(color: color),
      ),
    );
  }
  /// Add custom item
  Future<void> _generateFromJson() async {
    final String jsonString =
        (await Clipboard.getData(Clipboard.kTextPlain))?.text ?? '';
    if (jsonString.isEmpty) {
      _showAlertDialog(
          title: 'Clipboard is empty',
          content: 'Please copy the json string to the clipboard first');
      return;
    }
    try {
      final List<dynamic> items = jsonDecode(jsonString) as List<dynamic>;

      for (final dynamic item in items) {
        if (item['type'] == 'StackTextItem') {
          _boardController.addItem(
            StackTextItem.fromJson(item),
          );
        } else if (item['type'] == 'StackImageItem') {
          _boardController.addItem(
            StackImageItem.fromJson(item),
          );
        } 
      }
    } catch (e) {
      _showAlertDialog(title: 'Error', content: e.toString());
    }
  }

  /// get json
  Future<void> _getJson() async {
    final String json = jsonEncode(_boardController.getAllData());
    Clipboard.setData(ClipboardData(text: json));
    showDialog<void>(
      context: context,
      builder: (_) {
        return AlertDialog(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
          title: Row(
            children: [
              Icon(Icons.code, color: Colors.blue[600]),
              const SizedBox(width: 8),
              const Text(
                'Export JSON Data',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
            ],
          ),
          content: Container(
            constraints: const BoxConstraints(maxHeight: 400, maxWidth: 500),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.all(12),
                  decoration: BoxDecoration(
                    color: Colors.grey[100],
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(color: Colors.grey[300]!),
                  ),
                  child: Row(
                    children: [
                      Icon(Icons.check_circle, color: Colors.green[600], size: 20),
                      const SizedBox(width: 8),
                      const Expanded(
                        child: Text(
                          'JSON data copied to clipboard!',
                          style: TextStyle(color: Colors.green, fontWeight: FontWeight.w500),
                        ),
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: 16),
                const Text(
                  'Preview:',
                  style: TextStyle(fontWeight: FontWeight.w600),
                ),
                const SizedBox(height: 8),
                Flexible(
                  child: Container(
                    padding: const EdgeInsets.all(12),
                    decoration: BoxDecoration(
                      color: Colors.grey[50],
                      borderRadius: BorderRadius.circular(8),
                      border: Border.all(color: Colors.grey[300]!),
                    ),
                    child: SingleChildScrollView(
                      child: Text(
                        json,
                        style: const TextStyle(
                          fontFamily: 'monospace',
                          fontSize: 12,
                        ),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('Close'),
            ),
          ],
        );
      },
    );
  }

  Future<void> _addImageFromGalleryItem() async {
    try {
      final ImagePicker picker = ImagePicker();
      final XFile? pickedImage =
          await picker.pickImage(source: ImageSource.gallery);

      if (pickedImage == null) return;

      final File imageFile = File(pickedImage.path);

      final imageItem = StackImageItem(
        size: const Size(300, 300),
        content: ImageItemContent(
          file: imageFile,
        ),
      );

      _boardController.addItem(imageItem);
    } catch (e) {
      debugPrint('Image pick error: $e');
    }
  }

  void _addSvgItem() {
    // Example SVG string - a simple star * replace the image in constants.dart with this           
     String svgString = SVG_ASSET_IMAGE_NAME;

    final svgItem = StackImageItem.svg(
      svgString: svgString,
      size: const Size(150, 150),
      fit: BoxFit.contain,
      semanticLabel: 'Golden Star',
    );

    _boardController.addItem(svgItem);
  }

  /// Example SVG from network
  void _addSvgNetworkItem() {
    final svgNetworkItem = StackImageItem(
      size: const Size(120, 120),
      content: ImageItemContent(
        url: SVG_NETWORK_IMAGE_URL,
      ),
    );

    _boardController.addItem(svgNetworkItem);
  }

  /// Example SVG from asset
  void _addAssetItem() {
    final svgAssetItem = StackImageItem(
      size: const Size(120, 120),
      content: ImageItemContent(
        assetName: ASSET_IMAGE_NAME,
      ),
    );

    _boardController.addItem(svgAssetItem);
  }
  /// Example for image from Network
  void _addNetworkItem() {
    
    final networkItem = StackImageItem(
      size: const Size(120, 120),
      content: ImageItemContent(
        url: NETWORK_IMAGE_URL,
      ),
    );

    _boardController.addItem(networkItem);
  }

  /// Background Editor Methods
  void _openBackgroundEditor() {
    showDialog(
      context: context,
      builder: (context) => _BackgroundEditorDialog(
        backgroundColor: _backgroundColor,
        backgroundGradient: _backgroundGradient,
        backgroundImage: _backgroundImage,
        backgroundWidth: _backgroundWidth,
        backgroundHeight: _backgroundHeight,
        backgroundFit: _backgroundFit,
        useGradient: _useGradient,
        useImage: _useImage,
        onSave: (
          Color color,
          Gradient? gradient,
          File? image,
          double width,
          double height,
          BoxFit fit,
          bool useGradient,
          bool useImage,
        ) {
          setState(() {
            _backgroundColor = color;
            _backgroundGradient = gradient;
            _backgroundImage = image;
            _backgroundWidth = width;
            _backgroundHeight = height;
            _backgroundFit = fit;
            _useGradient = useGradient;
            _useImage = useImage;
          });
        },
      ),
    );
  }

  Widget _buildBackground() {
    if (_useImage && _backgroundImage != null) {
      return Container(
        width: _backgroundWidth,
        height: _backgroundHeight,
        decoration: BoxDecoration(
          image: DecorationImage(
            image: FileImage(_backgroundImage!),
            fit: _backgroundFit,
          ),
        ),
      );
    } else if (_useGradient && _backgroundGradient != null) {
      return Container(
        width: _backgroundWidth,
        height: _backgroundHeight,
        decoration: BoxDecoration(
          gradient: _backgroundGradient,
        ),
      );
    } else {
      return Container(
        width: _backgroundWidth,
        height: _backgroundHeight,
        decoration: BoxDecoration(
          color: _backgroundColor,
        ),
      );
    }
  }

 

  


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: const Text(
          'Stack Board Plus Example',
          style: TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
            color: Colors.white,
          ),
        ),
        elevation: 0,
        flexibleSpace: Container(
          decoration: const BoxDecoration(
            gradient: LinearGradient(
              colors: [Colors.blue, Colors.purple],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
          ),
        ),
        actions: [
          IconButton(
            onPressed: _openBackgroundEditor,
            icon: const Icon(Icons.wallpaper, color: Colors.white),
            tooltip: 'Background Settings',
          ),
          IconButton(
            onPressed: () => _boardController.clear(),
            icon: const Icon(Icons.clear_all, color: Colors.white),
            tooltip: 'Clear All',
          ),
          const SizedBox(width: 8),
        ],
      ),
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.grey[50]!, Colors.grey[100]!],
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
          ),
        ),
        child: StackBoardPlus(
          onDel: _onDel,
          controller: _boardController,
          caseStyle: CaseStyle(
            frameBorderColor: Colors.blue.withOpacity(0.6),
            buttonIconColor: Colors.white,
            buttonBgColor: Colors.blue,
            buttonBorderColor: Colors.blue[700]!,
            frameBorderWidth: 2,
            buttonSize: 32,
          ),
          background: _buildBackground(),
          customBuilder: (StackItem<StackItemContent> item) {
            if (item is StackTextItem) {
              return Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.1),
                      blurRadius: 8,
                      offset: const Offset(0, 4),
                    ),
                  ],
                ),
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: _EnhancedStackTextCase(
                    item: item, 
                    decoration: InputDecoration.collapsed(hintText: "Enter text"),
                    onTap: () => _openTextCustomizationDialog(item),
                  ),
                ),
              );
            } else if (item is StackImageItem) {
              return Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.15),
                      blurRadius: 10,
                      offset: const Offset(0, 4),
                    ),
                  ],
                ),
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(12),
                  child: StackImageCase(item: item),
                ),
              );
            } else if (item is ColorStackItem) {
              return Container(
                width: item.size.width,
                height: item.size.height,
                decoration: BoxDecoration(
                  color: item.content?.color,
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.2),
                      blurRadius: 8,
                      offset: const Offset(0, 4),
                    ),
                  ],
                ),
              );
            }

            return const SizedBox.shrink();
          },
        ),
      ),
      bottomNavigationBar: Container(
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: Colors.white,
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.1),
              blurRadius: 10,
              offset: const Offset(0, -2),
            ),
          ],
        ),
        child: SafeArea(
          child: SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              spacing: 5,
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                _buildActionButton(
                  icon: Icons.text_fields,
                  label: 'Text',
                  color: Colors.blue,
                  onPressed: _addTextItem,
                ),
                _buildActionButton(
                  icon: Icons.image,
                  label: 'Network Image',
                  color: Colors.green,
                  onPressed: _addNetworkItem,
                ),
                _buildActionButton(
                  icon: Icons.image,
                  label: 'Asset Image',
                  color: Colors.green,
                  onPressed: _addAssetItem,
                ),
                _buildActionButton(
                  icon: Icons.add_photo_alternate,
                  label: 'Gallery',
                  color: Colors.purple,
                  onPressed: _addImageFromGalleryItem,
                ),
                _buildActionButton(
                  icon: Icons.star,
                  label: 'SVG',
                  color: Colors.amber,
                  onPressed: _addSvgItem,
                ),
                _buildActionButton(
                  icon: Icons.file_upload,
                  label: 'SVG Network',
                  color: Colors.blue,
                  onPressed: _addSvgNetworkItem,
                ),
                _buildActionButton(
                  icon: Icons.palette,
                  label: 'Color',
                  color: Colors.orange,
                  onPressed: _addCustomItem,
                ),
                _buildActionButton(
                  icon: Icons.file_download,
                  label: 'Export',
                  color: Colors.teal,
                  onPressed: _getJson,
                ),
                _buildActionButton(
                  icon: Icons.file_upload,
                  label: 'Import',
                  color: Colors.indigo,
                  onPressed: _generateFromJson,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildActionButton({
    required IconData icon,
    required String label,
    required Color color,
    required VoidCallback onPressed,
  }) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Container(
          decoration: BoxDecoration(
            color: color.withOpacity(0.1),
            borderRadius: BorderRadius.circular(12),
            border: Border.all(color: color.withOpacity(0.3)),
          ),
          child: IconButton(
            onPressed: onPressed,
            icon: Icon(icon, color: color, size: 24),
            padding: const EdgeInsets.all(12),
          ),
        ),
        const SizedBox(height: 4),
        Text(
          label,
          style: TextStyle(
            fontSize: 11,
            color: color,
            fontWeight: FontWeight.w600,
          ),
        ),
      ],
    );
  }

  // Text Customization Methods
  void _openTextCustomizationDialog(StackTextItem item) {
    showDialog(
      context: context,
      builder: (context) => _TextCustomizationDialog(
        item: item,
        onSave: (updatedContent) {
          setState(() {
            // Create a new item with updated content
            final updatedItem = item.copyWith(content: updatedContent);
            _boardController.removeById(item.id);
            _boardController.addItem(updatedItem);
          });
        },
      ),
    );
  }

  void _showAlertDialog({required String title, required String content}) {
    showDialog<void>(
      context: context,
      builder: (_) {
        return AlertDialog(
          title: Text(title),
          content: Text(content),
          actions: <Widget>[
            TextButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('OK'),
            ),
          ],
        );
      },
    );
  }
}

class _EnhancedStackTextCase extends StatefulWidget {
  const _EnhancedStackTextCase({
    Key? key,
    required this.item,
    this.decoration,
    this.onTap,
  }) : super(key: key);

  final StackTextItem item;
  final InputDecoration? decoration;
  final VoidCallback? onTap;

  @override
  _EnhancedStackTextCaseState createState() => _EnhancedStackTextCaseState();
}

class _EnhancedStackTextCaseState extends State<_EnhancedStackTextCase> {
  bool _isHovered = false;

  TextItemContent? get content => widget.item.content;

  @override
  Widget build(BuildContext context) {
    return widget.item.status == StackItemStatus.editing
        ? _buildEditing(context)
        : _buildNormal(context);
  }

  Widget _buildNormal(BuildContext context) {
    final content = this.content;
    if (content == null) return const SizedBox.shrink();

    // Build the base text widget with enhanced styling
    Widget textWidget = _buildEnhancedText(content);

    // Apply container styling (background, border, padding)
    if (content.backgroundColor != null || 
        content.borderWidth > 0 || 
        content.padding != null) {
      textWidget = Container(
        padding: content.padding ?? EdgeInsets.zero,
        decoration: BoxDecoration(
          color: content.backgroundColor,
          border: content.borderWidth > 0 && content.borderColor != null
              ? Border.all(color: content.borderColor!, width: content.borderWidth)
              : null,
          borderRadius: BorderRadius.circular(4),
        ),
        child: textWidget,
      );
    }

    // Apply margin
    if (content.margin != null) {
      textWidget = Padding(
        padding: content.margin!,
        child: textWidget,
      );
    }

    // Apply transformations (skew, flip)
    if (content.skewX != 0 || content.skewY != 0 || 
        content.flipHorizontally || content.flipVertically) {
      textWidget = Transform(
        transform: Matrix4.identity()
          ..setEntry(0, 1, content.skewX)
          ..setEntry(1, 0, content.skewY)
          ..scale(
            content.flipHorizontally ? -1.0 : 1.0, 
            content.flipVertically ? -1.0 : 1.0
          ),
        alignment: Alignment.center,
        child: textWidget,
      );
    }

    // Apply opacity
    if (content.opacity < 1.0) {
      textWidget = Opacity(
        opacity: content.opacity,
        child: textWidget,
      );
    }

    // Apply arc transformation if needed
    if (content.arcDegree != 0) {
      textWidget = Transform.rotate(
        angle: content.arcDegree * (3.14159 / 180), // Convert degrees to radians
        child: textWidget,
      );
    }

    // Add tap gesture for customization
    if (widget.onTap != null) {
      textWidget = MouseRegion(
        onEnter: (_) => setState(() => _isHovered = true),
        onExit: (_) => setState(() => _isHovered = false),
        child: GestureDetector(
          onTap: widget.onTap,
          onDoubleTap: widget.onTap, // Also respond to double tap
          child: Stack(
            children: [
              Container(
                decoration: BoxDecoration(
                  border: Border.all(
                    color: _isHovered ? Colors.blue.withOpacity(0.3) : Colors.transparent, 
                    width: 2
                  ),
                  borderRadius: BorderRadius.circular(4),
                ),
                child: textWidget,
              ),
              if (_isHovered)
                Positioned(
                  top: -8,
                  right: -8,
                  child: Container(
                    padding: const EdgeInsets.all(4),
                    decoration: BoxDecoration(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(12),
                      boxShadow: [
                        BoxShadow(
                          color: Colors.black.withOpacity(0.2),
                          blurRadius: 4,
                          offset: const Offset(0, 2),
                        ),
                      ],
                    ),
                    child: const Icon(
                      Icons.edit,
                      color: Colors.white,
                      size: 12,
                    ),
                  ),
                ),
            ],
          ),
        ),
      );
    }

    // Wrap in alignment container
    return Container(
      alignment: _getAlignment(content.horizontalAlignment, content.verticalAlignment),
      child: FittedBox(child: textWidget),
    );
  }

  Widget _buildEditing(BuildContext context) {
    // When in editing mode, immediately open the customization dialog
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (widget.onTap != null) {
        widget.onTap!();
      }
    });
    
    return Center(
      child: Container(
        padding: const EdgeInsets.all(8),
        decoration: BoxDecoration(
          border: Border.all(color: Colors.blue, width: 2),
          borderRadius: BorderRadius.circular(4),
          color: Colors.blue.withOpacity(0.1),
        ),
        child: Text(
          content?.data ?? 'Tap to customize',
          style: content?.style?.copyWith(color: Colors.blue) ?? 
                 const TextStyle(color: Colors.blue),
          textAlign: content?.textAlign ?? TextAlign.start,
        ),
      ),
    );
  }

  Widget _buildEnhancedText(TextItemContent content) {
    // Create text style with enhanced properties using Google Fonts
    TextStyle baseStyle;
    
    try {
      baseStyle = GoogleFonts.getFont(
        content.fontFamily ?? 'Roboto',
        fontSize: content.fontSize,
        fontWeight: content.fontWeight,
        fontStyle: content.fontStyle,
        letterSpacing: content.letterSpacing,
        wordSpacing: content.wordSpacing,
        height: content.lineHeight,
        decoration: content.isUnderlined ? TextDecoration.underline : null,
        decorationColor: content.textColor,
        shadows: _buildShadows(content),
      );
    } catch (e) {
      // Fallback to default font if Google Font fails
      baseStyle = TextStyle(
        fontFamily: content.fontFamily,
        fontSize: content.fontSize,
        fontWeight: content.fontWeight,
        fontStyle: content.fontStyle,
        letterSpacing: content.letterSpacing,
        wordSpacing: content.wordSpacing,
        height: content.lineHeight,
        decoration: content.isUnderlined ? TextDecoration.underline : null,
        decorationColor: content.textColor,
        shadows: _buildShadows(content),
      );
    }

    // Combine with existing style if any
    final finalStyle = content.style?.merge(baseStyle) ?? baseStyle;

    final text = content.data ?? '';

    // Handle gradient text
    if (content.textGradient != null) {
      return ShaderMask(
        shaderCallback: (bounds) => content.textGradient!.createShader(bounds),
        child: Text(
          text,
          style: finalStyle.copyWith(color: Colors.white),
          textAlign: content.textAlign ?? content.horizontalAlignment,
          textDirection: content.textDirection,
          locale: content.locale,
          softWrap: content.softWrap,
          overflow: content.overflow,
          textScaler: content.textScaleFactor != null
              ? TextScaler.linear(content.textScaleFactor!)
              : TextScaler.noScaling,
          maxLines: content.maxLines,
          semanticsLabel: content.semanticsLabel,
          textWidthBasis: content.textWidthBasis,
          textHeightBehavior: content.textHeightBehavior,
          selectionColor: content.selectionColor,
        ),
      );
    } else {
      // Regular text with color
      return Text(
        text,
        style: finalStyle.copyWith(
          color: content.textColor?.withValues(alpha: content.opacity) ?? 
                 finalStyle.color?.withValues(alpha: content.opacity),
        ),
        textAlign: content.textAlign ?? content.horizontalAlignment,
        textDirection: content.textDirection,
        locale: content.locale,
        softWrap: content.softWrap,
        overflow: content.overflow,
        textScaler: content.textScaleFactor != null
            ? TextScaler.linear(content.textScaleFactor!)
            : TextScaler.noScaling,
        maxLines: content.maxLines,
        semanticsLabel: content.semanticsLabel,
        textWidthBasis: content.textWidthBasis,
        textHeightBehavior: content.textHeightBehavior,
        selectionColor: content.selectionColor,
      );
    }
  }

  List<Shadow>? _buildShadows(TextItemContent content) {
    if (content.shadowBlurRadius <= 0 || content.shadowColor == null) {
      return null;
    }

    return [
      Shadow(
        color: content.shadowColor!,
        offset: content.shadowOffset ?? const Offset(1, 1),
        blurRadius: content.shadowBlurRadius,
      ),
    ];
  }

  Alignment _getAlignment(TextAlign horizontal, MainAxisAlignment vertical) {
    double x = 0.0;
    double y = 0.0;

    // Horizontal alignment
    switch (horizontal) {
      case TextAlign.left:
        x = -1.0;
        break;
      case TextAlign.center:
        x = 0.0;
        break;
      case TextAlign.right:
        x = 1.0;
        break;
      case TextAlign.start:
        x = -1.0;
        break;
      case TextAlign.end:
        x = 1.0;
        break;
      case TextAlign.justify:
        x = 0.0;
        break;
    }

    // Vertical alignment
    switch (vertical) {
      case MainAxisAlignment.start:
        y = -1.0;
        break;
      case MainAxisAlignment.center:
        y = 0.0;
        break;
      case MainAxisAlignment.end:
        y = 1.0;
        break;
      case MainAxisAlignment.spaceAround:
      case MainAxisAlignment.spaceBetween:
      case MainAxisAlignment.spaceEvenly:
        y = 0.0;
        break;
    }

    return Alignment(x, y);
  }
}

class _TextCustomizationDialog extends StatefulWidget {
  final StackTextItem item;
  final Function(TextItemContent) onSave;

  const _TextCustomizationDialog({
    Key? key,
    required this.item,
    required this.onSave,
  }) : super(key: key);

  @override
  _TextCustomizationDialogState createState() => _TextCustomizationDialogState();
}

class _TextCustomizationDialogState extends State<_TextCustomizationDialog> {
  late TextEditingController _textController;
  
  // Text properties
  String _fontFamily = 'Roboto';
  double _fontSize = 16.0;
  FontWeight _fontWeight = FontWeight.normal;
  FontStyle _fontStyle = FontStyle.normal;
  bool _isUnderlined = false;
  Color _textColor = Colors.black;
  bool _useGradientText = false;
  Gradient? _textGradient;
  
  // Stroke
  Color _strokeColor = Colors.transparent;
  double _strokeWidth = 0.0;
  
  // Shadow
  Color _shadowColor = Colors.black54;
  Offset _shadowOffset = const Offset(1, 1);
  double _shadowBlurRadius = 2.0;
  double _shadowSpreadRadius = 0.0;
  
  // Arc and spacing
  double _arcDegree = 0.0;
  double _letterSpacing = 0.0;
  double _wordSpacing = 0.0;
  double _lineHeight = 1.0;
  
  // Background and border
  Color? _backgroundColor;
  Color? _borderColor;
  double _borderWidth = 0.0;
  
  // Transform and alignment
  double _opacity = 1.0;
  EdgeInsets _padding = EdgeInsets.zero;
  EdgeInsets _margin = EdgeInsets.zero;
  double _skewX = 0.0;
  double _skewY = 0.0;
  TextAlign _horizontalAlignment = TextAlign.center;
  MainAxisAlignment _verticalAlignment = MainAxisAlignment.center;
  bool _flipHorizontally = false;
  bool _flipVertically = false;
  
  int _selectedTab = 0;

  // Google Fonts list (20 popular fonts)
  final List<String> _fontFamilies = [
    'Roboto', 'Open Sans', 'Lato', 'Montserrat', 'Oswald',
     'Raleway', 'Poppins', 'Merriweather', 'Ubuntu',
    'Playfair Display', 'Nunito', 'PT Sans', 'Crimson Text', 'Libre Baskerville',
    'Dancing Script', 'Pacifico', 'Lobster', 'Great Vibes', 'Indie Flower'
  ];

  @override
  void initState() {
    super.initState();
    _initializeFromItem();
  }

  void _initializeFromItem() {
    final content = widget.item.content;
    if (content != null) {
      _textController = TextEditingController(text: content.data ?? '');
      _fontFamily = content.fontFamily ?? 'Roboto';
      _fontSize = content.fontSize;
      _fontWeight = content.fontWeight ?? FontWeight.normal;
      _fontStyle = content.fontStyle ?? FontStyle.normal;
      _isUnderlined = content.isUnderlined;
      _textColor = content.textColor ?? Colors.black;
      _textGradient = content.textGradient;
      _useGradientText = content.textGradient != null;
      _strokeColor = content.strokeColor ?? Colors.transparent;
      _strokeWidth = content.strokeWidth;
      _shadowColor = content.shadowColor ?? Colors.black54;
      _shadowOffset = content.shadowOffset ?? const Offset(1, 1);
      _shadowBlurRadius = content.shadowBlurRadius;
      _shadowSpreadRadius = content.shadowSpreadRadius;
      _arcDegree = content.arcDegree;
      _letterSpacing = content.letterSpacing;
      _wordSpacing = content.wordSpacing;
      _lineHeight = content.lineHeight;
      _backgroundColor = content.backgroundColor;
      _borderColor = content.borderColor;
      _borderWidth = content.borderWidth;
      _opacity = content.opacity;
      _padding = content.padding ?? EdgeInsets.zero;
      _margin = content.margin ?? EdgeInsets.zero;
      _skewX = content.skewX;
      _skewY = content.skewY;
      _horizontalAlignment = content.horizontalAlignment;
      _verticalAlignment = content.verticalAlignment;
      _flipHorizontally = content.flipHorizontally;
      _flipVertically = content.flipVertically;
    } else {
      _textController = TextEditingController(text: 'Sample Text');
    }
  }

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Dialog(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: Container(
        width: MediaQuery.of(context).size.width * 0.95,
        height: MediaQuery.of(context).size.height * 0.9,
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            // Header
            Row(
              children: [
                const Text(
                  'Text Customization',
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                ),
                const Spacer(),
                IconButton(
                  onPressed: () => Navigator.pop(context),
                  icon: const Icon(Icons.close),
                ),
              ],
            ),
            const SizedBox(height: 16),

            // Text Input
            TextField(
              controller: _textController,
              decoration: const InputDecoration(
                labelText: 'Text Content',
                border: OutlineInputBorder(),
              ),
              maxLines: 2,
              onChanged: (value) => setState(() {}),
            ),
            const SizedBox(height: 16),

            // Preview
            Container(
              height: 80,
              width: double.infinity,
              decoration: BoxDecoration(
                color: Colors.grey[100],
                borderRadius: BorderRadius.circular(8),
                border: Border.all(color: Colors.grey[300]!),
              ),
              child: Center(child: _buildPreviewText()),
            ),
            const SizedBox(height: 16),

            // Tab Bar
            Container(
              decoration: BoxDecoration(
                color: Colors.grey[200],
                borderRadius: BorderRadius.circular(8),
              ),
              child: Row(
                children: [
                  _buildTabButton('Font', 0, Icons.font_download),
                  _buildTabButton('Style', 1, Icons.format_paint),
                  _buildTabButton('Effects', 2, Icons.auto_awesome),
                  _buildTabButton('Layout', 3, Icons.view_agenda),
                ],
              ),
            ),
            const SizedBox(height: 16),

            // Content
            Expanded(
              child: _buildTabContent(),
            ),

            // Buttons
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: OutlinedButton(
                    onPressed: () => Navigator.pop(context),
                    child: const Text('Cancel'),
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: ElevatedButton(
                    onPressed: _saveSettings,
                    child: const Text('Apply'),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildTabButton(String title, int index, IconData icon) {
    final isSelected = _selectedTab == index;
    return Expanded(
      child: GestureDetector(
        onTap: () => setState(() => _selectedTab = index),
        child: Container(
          padding: const EdgeInsets.symmetric(vertical: 12),
          decoration: BoxDecoration(
            color: isSelected ? Colors.blue : Colors.transparent,
            borderRadius: BorderRadius.circular(8),
          ),
          child: Column(
            children: [
              Icon(
                icon,
                color: isSelected ? Colors.white : Colors.grey[600],
                size: 20,
              ),
              const SizedBox(height: 4),
              Text(
                title,
                style: TextStyle(
                  color: isSelected ? Colors.white : Colors.grey[600],
                  fontSize: 12,
                  fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildTabContent() {
    switch (_selectedTab) {
      case 0:
        return _buildFontTab();
      case 1:
        return _buildStyleTab();
      case 2:
        return _buildEffectsTab();
      case 3:
        return _buildLayoutTab();
      default:
        return const SizedBox();
    }
  }

  Widget _buildFontTab() {
    return SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Font Family
          const Text('Font Family', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Container(
            height: 120,
            child: GridView.builder(
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                childAspectRatio: 3,
                crossAxisSpacing: 8,
                mainAxisSpacing: 8,
              ),
              itemCount: _fontFamilies.length,
              itemBuilder: (context, index) {
                final font = _fontFamilies[index];
                return GestureDetector(
                  onTap: () => setState(() => _fontFamily = font),
                  child: Container(
                    decoration: BoxDecoration(
                      border: Border.all(
                        color: _fontFamily == font ? Colors.blue : Colors.grey[300]!,
                        width: _fontFamily == font ? 2 : 1,
                      ),
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Center(
                      child: Text(
                        font,
                        style: GoogleFonts.getFont(font, fontSize: 12),
                        overflow: TextOverflow.ellipsis,
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
          const SizedBox(height: 16),

          // Font Size
          const Text('Font Size', style: TextStyle(fontWeight: FontWeight.bold)),
          Row(
            children: [
              Expanded(
                child: Slider(
                  value: _fontSize,
                  min: 8,
                  max: 100,
                  divisions: 92,
                  label: _fontSize.toInt().toString(),
                  onChanged: (value) => setState(() => _fontSize = value),
                ),
              ),
              SizedBox(
                width: 50,
                child: Text('${_fontSize.toInt()}px'),
              ),
            ],
          ),
          const SizedBox(height: 16),

          // Font Weight
          const Text('Font Weight', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Wrap(
            spacing: 8,
            children: [
              FontWeight.w100,
              FontWeight.w300,
              FontWeight.normal,
              FontWeight.w500,
              FontWeight.w600,
              FontWeight.bold,
              FontWeight.w800,
              FontWeight.w900,
            ].map((weight) => ChoiceChip(
              label: Text('${weight.index + 1}00'),
              selected: _fontWeight == weight,
              onSelected: (selected) => setState(() => _fontWeight = weight),
            )).toList(),
          ),
        ],
      ),
    );
  }

  Widget _buildStyleTab() {
    return SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Format Options
          const Text('Format', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          CheckboxListTile(
            title: const Text('Bold'),
            value: _fontWeight == FontWeight.bold,
            onChanged: (value) => setState(() => 
              _fontWeight = value! ? FontWeight.bold : FontWeight.normal),
          ),
          CheckboxListTile(
            title: const Text('Italic'),
            value: _fontStyle == FontStyle.italic,
            onChanged: (value) => setState(() => 
              _fontStyle = value! ? FontStyle.italic : FontStyle.normal),
          ),
          CheckboxListTile(
            title: const Text('Underline'),
            value: _isUnderlined,
            onChanged: (value) => setState(() => _isUnderlined = value!),
          ),
          const SizedBox(height: 16),

          // Text Color
          const Text('Text Color', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Row(
            children: [
              Switch(
                value: _useGradientText,
                onChanged: (value) => setState(() => _useGradientText = value),
              ),
              Text(_useGradientText ? 'Gradient' : 'Solid Color'),
            ],
          ),
          const SizedBox(height: 8),
          
          if (!_useGradientText) ...[
            // Solid Colors
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                Colors.black, Colors.white, Colors.red, Colors.green,
                Colors.blue, Colors.yellow, Colors.purple, Colors.orange,
                Colors.pink, Colors.teal, Colors.grey, Colors.brown,
              ].map((color) => GestureDetector(
                onTap: () => setState(() => _textColor = color),
                child: Container(
                  width: 40,
                  height: 40,
                  decoration: BoxDecoration(
                    color: color,
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(
                      color: _textColor == color ? Colors.blue : Colors.grey[300]!,
                      width: _textColor == color ? 3 : 1,
                    ),
                  ),
                ),
              )).toList(),
            ),
          ] else ...[
            // Gradient Options
            Text('Gradient Presets'),
            const SizedBox(height: 8),
            Container(
              height: 60,
              child: ListView.builder(
                scrollDirection: Axis.horizontal,
                itemCount: 6,
                itemBuilder: (context, index) {
                  final gradients = [
                    const LinearGradient(colors: [Colors.red, Colors.orange]),
                    const LinearGradient(colors: [Colors.blue, Colors.purple]),
                    const LinearGradient(colors: [Colors.green, Colors.teal]),
                    const LinearGradient(colors: [Colors.pink, Colors.purple]),
                    const LinearGradient(colors: [Colors.yellow, Colors.orange]),
                    const LinearGradient(colors: [Colors.indigo, Colors.blue]),
                  ];
                  final gradient = gradients[index];
                  return GestureDetector(
                    onTap: () => setState(() => _textGradient = gradient),
                    child: Container(
                      width: 60,
                      height: 40,
                      margin: const EdgeInsets.only(right: 8),
                      decoration: BoxDecoration(
                        gradient: gradient,
                        borderRadius: BorderRadius.circular(8),
                        border: Border.all(
                          color: _textGradient == gradient ? Colors.blue : Colors.grey[300]!,
                          width: _textGradient == gradient ? 3 : 1,
                        ),
                      ),
                    ),
                  );
                },
              ),
            ),
          ],
          const SizedBox(height: 16),

          // Opacity
          const Text('Opacity', style: TextStyle(fontWeight: FontWeight.bold)),
          Slider(
            value: _opacity,
            min: 0.1,
            max: 1.0,
            divisions: 18,
            label: '${(_opacity * 100).toInt()}%',
            onChanged: (value) => setState(() => _opacity = value),
          ),
        ],
      ),
    );
  }

  Widget _buildEffectsTab() {
    return SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Stroke
          const Text('Stroke', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Row(
            children: [
              const Text('Width:'),
              Expanded(
                child: Slider(
                  value: _strokeWidth,
                  min: 0,
                  max: 10,
                  divisions: 20,
                  label: _strokeWidth.toStringAsFixed(1),
                  onChanged: (value) => setState(() => _strokeWidth = value),
                ),
              ),
            ],
          ),
          if (_strokeWidth > 0) ...[
            const Text('Stroke Color:'),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              children: [
                Colors.black, Colors.white, Colors.red, Colors.green,
                Colors.blue, Colors.yellow, Colors.purple, Colors.orange,
              ].map((color) => GestureDetector(
                onTap: () => setState(() => _strokeColor = color),
                child: Container(
                  width: 30,
                  height: 30,
                  decoration: BoxDecoration(
                    color: color,
                    borderRadius: BorderRadius.circular(6),
                    border: Border.all(
                      color: _strokeColor == color ? Colors.blue : Colors.grey[300]!,
                      width: _strokeColor == color ? 2 : 1,
                    ),
                  ),
                ),
              )).toList(),
            ),
          ],
          const SizedBox(height: 16),

          // Shadow
          const Text('Shadow', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Row(
            children: [
              const Text('Blur:'),
              Expanded(
                child: Slider(
                  value: _shadowBlurRadius,
                  min: 0,
                  max: 20,
                  divisions: 20,
                  label: _shadowBlurRadius.toStringAsFixed(1),
                  onChanged: (value) => setState(() => _shadowBlurRadius = value),
                ),
              ),
            ],
          ),
          Row(
            children: [
              const Text('Spread:'),
              Expanded(
                child: Slider(
                  value: _shadowSpreadRadius,
                  min: 0,
                  max: 10,
                  divisions: 20,
                  label: _shadowSpreadRadius.toStringAsFixed(1),
                  onChanged: (value) => setState(() => _shadowSpreadRadius = value),
                ),
              ),
            ],
          ),
          Row(
            children: [
              const Text('Offset X:'),
              Expanded(
                child: Slider(
                  value: _shadowOffset.dx,
                  min: -10,
                  max: 10,
                  divisions: 20,
                  label: _shadowOffset.dx.toStringAsFixed(1),
                  onChanged: (value) => setState(() => 
                    _shadowOffset = Offset(value, _shadowOffset.dy)),
                ),
              ),
            ],
          ),
          Row(
            children: [
              const Text('Offset Y:'),
              Expanded(
                child: Slider(
                  value: _shadowOffset.dy,
                  min: -10,
                  max: 10,
                  divisions: 20,
                  label: _shadowOffset.dy.toStringAsFixed(1),
                  onChanged: (value) => setState(() => 
                    _shadowOffset = Offset(_shadowOffset.dx, value)),
                ),
              ),
            ],
          ),

          // Arc
          const SizedBox(height: 16),
          const Text('Text Arc', style: TextStyle(fontWeight: FontWeight.bold)),
          Slider(
            value: _arcDegree,
            min: -180,
            max: 180,
            divisions: 72,
            label: '${_arcDegree.toInt()}°',
            onChanged: (value) => setState(() => _arcDegree = value),
          ),

          // Spacing
          const SizedBox(height: 16),
          const Text('Letter Spacing', style: TextStyle(fontWeight: FontWeight.bold)),
          Slider(
            value: _letterSpacing,
            min: -5,
            max: 10,
            divisions: 30,
            label: _letterSpacing.toStringAsFixed(1),
            onChanged: (value) => setState(() => _letterSpacing = value),
          ),

          const Text('Word Spacing', style: TextStyle(fontWeight: FontWeight.bold)),
          Slider(
            value: _wordSpacing,
            min: -5,
            max: 10,
            divisions: 30,
            label: _wordSpacing.toStringAsFixed(1),
            onChanged: (value) => setState(() => _wordSpacing = value),
          ),

          const Text('Line Height', style: TextStyle(fontWeight: FontWeight.bold)),
          Slider(
            value: _lineHeight,
            min: 0.5,
            max: 3.0,
            divisions: 25,
            label: _lineHeight.toStringAsFixed(1),
            onChanged: (value) => setState(() => _lineHeight = value),
          ),
        ],
      ),
    );
  }

  Widget _buildLayoutTab() {
    return SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Background
          const Text('Background Color', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,

            child: Row(
              children: [
                GestureDetector(
                  onTap: () => setState(() => _backgroundColor = null),
                  child: Container(
                    width: 40,
                    height: 40,
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.grey),
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: const Icon(Icons.clear, size: 20),
                  ),
                ),
                const SizedBox(width: 8),
                ...([
                  Colors.white, Colors.black.withOpacity(0.1), Colors.red.withOpacity(0.1),
                  Colors.green.withOpacity(0.1), Colors.blue.withOpacity(0.1), Colors.yellow.withOpacity(0.1),
                ].map((color) => GestureDetector(
                  onTap: () => setState(() => _backgroundColor = color),
                  child: Container(
                    width: 40,
                    height: 40,
                    margin: const EdgeInsets.only(right: 8),
                    decoration: BoxDecoration(
                      color: color,
                      borderRadius: BorderRadius.circular(8),
                      border: Border.all(
                        color: _backgroundColor == color ? Colors.blue : Colors.grey[300]!,
                        width: _backgroundColor == color ? 2 : 1,
                      ),
                    ),
                  ),
                ))),
              ],
            ),
          ),
          const SizedBox(height: 16),

          // Border
          const Text('Border', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Row(
            children: [
              const Text('Width:'),
              Expanded(
                child: Slider(
                  value: _borderWidth,
                  min: 0,
                  max: 10,
                  divisions: 20,
                  label: _borderWidth.toStringAsFixed(1),
                  onChanged: (value) => setState(() => _borderWidth = value),
                ),
              ),
            ],
          ),
          if (_borderWidth > 0) ...[
            const Text('Border Color:'),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              children: [
                Colors.black, Colors.grey, Colors.red, Colors.green,
                Colors.blue, Colors.purple,
              ].map((color) => GestureDetector(
                onTap: () => setState(() => _borderColor = color),
                child: Container(
                  width: 30,
                  height: 30,
                  decoration: BoxDecoration(
                    color: color,
                    borderRadius: BorderRadius.circular(6),
                    border: Border.all(
                      color: _borderColor == color ? Colors.blue : Colors.grey[300]!,
                      width: _borderColor == color ? 2 : 1,
                    ),
                  ),
                ),
              )).toList(),
            ),
          ],
          const SizedBox(height: 16),

          // Padding & Margin
          const Text('Padding', style: TextStyle(fontWeight: FontWeight.bold)),
          Row(
            children: [
              Expanded(
                child: Column(
                  children: [
                    const Text('All'),
                    Slider(
                      value: _padding.left,
                      min: 0,
                      max: 20,
                      divisions: 20,
                      onChanged: (value) => setState(() => 
                        _padding = EdgeInsets.all(value)),
                    ),
                  ],
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),

          // Transform
          const Text('Transform', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Row(
            children: [
              const Text('Skew X:'),
              Expanded(
                child: Slider(
                  value: _skewX,
                  min: -1,
                  max: 1,
                  divisions: 20,
                  label: _skewX.toStringAsFixed(2),
                  onChanged: (value) => setState(() => _skewX = value),
                ),
              ),
            ],
          ),
          Row(
            children: [
              const Text('Skew Y:'),
              Expanded(
                child: Slider(
                  value: _skewY,
                  min: -1,
                  max: 1,
                  divisions: 20,
                  label: _skewY.toStringAsFixed(2),
                  onChanged: (value) => setState(() => _skewY = value),
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),

          // Alignment
          const Text('Alignment', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 8),
          Row(
            children: [
              const Text('Horizontal:'),
              const SizedBox(width: 8),
              Expanded(
              child: SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                child: Row(
                children: [TextAlign.left, TextAlign.center, TextAlign.right, TextAlign.start, TextAlign.end, TextAlign.justify].map((align) =>
                  Padding(
                  padding: const EdgeInsets.only(right: 8.0),
                  child: ChoiceChip(
                    label: Text(align.toString().split('.').last),
                    selected: _horizontalAlignment == align,
                    onSelected: (selected) => setState(() => _horizontalAlignment = align),
                  ),
                  ),
                ).toList(),
                ),
              ),
              ),
            ],
          ),
          const SizedBox(height: 8),
          Row(
            children: [
              const Text('Vertical:'),
              const SizedBox(width: 8),
              Expanded(
              child: SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                child: Row(
                children: [MainAxisAlignment.start, MainAxisAlignment.center, MainAxisAlignment.end].map((align) =>
                  Padding(
                  padding: const EdgeInsets.only(right: 8.0),
                  child: ChoiceChip(
                    label: Text(align.toString().split('.').last),
                    selected: _verticalAlignment == align,
                    onSelected: (selected) => setState(() => _verticalAlignment = align),
                  ),
                  ),
                ).toList(),
                ),
              ),
              ),
            ],
          ),
          const SizedBox(height: 16),

          // Flip
          const Text('Flip', style: TextStyle(fontWeight: FontWeight.bold)),
          Column(
            children: [
              CheckboxListTile(
                title: const Text('Horizontal'),
                value: _flipHorizontally,
                onChanged: (value) => setState(() => _flipHorizontally = value!),
              ),
              CheckboxListTile(
                title: const Text('Vertical'),
                value: _flipVertically,
                onChanged: (value) => setState(() => _flipVertically = value!),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildPreviewText() {
    final textWidget = Text(
      _textController.text.isEmpty ? 'Sample Text' : _textController.text,
      style: GoogleFonts.getFont(
        _fontFamily,
        fontSize: _fontSize * 0.7, // Scale down for preview
        fontWeight: _fontWeight,
        fontStyle: _fontStyle,
        color: _useGradientText ? null : _textColor.withValues(alpha: _opacity),
        letterSpacing: _letterSpacing,
        wordSpacing: _wordSpacing,
        height: _lineHeight,
        decoration: _isUnderlined ? TextDecoration.underline : null,
        decorationColor: _textColor,
        shadows: _shadowBlurRadius > 0 ? [
          Shadow(
            color: _shadowColor,
            offset: _shadowOffset,
            blurRadius: _shadowBlurRadius,
          ),
        ] : null,
      ),
      textAlign: _horizontalAlignment,
    );

    Widget result = textWidget;

    // Apply gradient if enabled
    if (_useGradientText && _textGradient != null) {
      result = ShaderMask(
        shaderCallback: (bounds) => _textGradient!.createShader(bounds),
        child: Text(
          _textController.text.isEmpty ? 'Sample Text' : _textController.text,
          style: GoogleFonts.getFont(
            _fontFamily,
            fontSize: _fontSize * 0.7,
            fontWeight: _fontWeight,
            fontStyle: _fontStyle,
            color: Colors.white,
            letterSpacing: _letterSpacing,
            wordSpacing: _wordSpacing,
            height: _lineHeight,
            decoration: _isUnderlined ? TextDecoration.underline : null,
          ),
          textAlign: _horizontalAlignment,
        ),
      );
    }

    // Apply background
    if (_backgroundColor != null) {
      result = Container(
        padding: _padding,
        decoration: BoxDecoration(
          color: _backgroundColor,
          border: _borderWidth > 0 ? Border.all(color: _borderColor!, width: _borderWidth) : null,
          borderRadius: BorderRadius.circular(4),
        ),
        child: result,
      );
    }

    // Apply transforms
    if (_skewX != 0 || _skewY != 0 || _flipHorizontally || _flipVertically) {
      result = Transform(
        transform: Matrix4.identity()
          ..setEntry(0, 1, _skewX)
          ..setEntry(1, 0, _skewY)
          ..scale(_flipHorizontally ? -1.0 : 1.0, _flipVertically ? -1.0 : 1.0),
        alignment: Alignment.center,
        child: result,
      );
    }

    return result;
  }

  void _saveSettings() {
    final content = TextItemContent(
      data: _textController.text,
      fontFamily: _fontFamily,
      fontSize: _fontSize,
      fontWeight: _fontWeight,
      fontStyle: _fontStyle,
      isUnderlined: _isUnderlined,
      textColor: _useGradientText ? null : _textColor,
      textGradient: _useGradientText ? _textGradient : null,
      strokeColor: _strokeWidth > 0 ? _strokeColor : null,
      strokeWidth: _strokeWidth,
      shadowColor: _shadowBlurRadius > 0 ? _shadowColor : null,
      shadowOffset: _shadowBlurRadius > 0 ? _shadowOffset : null,
      shadowBlurRadius: _shadowBlurRadius,
      shadowSpreadRadius: _shadowSpreadRadius,
      arcDegree: _arcDegree,
      letterSpacing: _letterSpacing,
      wordSpacing: _wordSpacing,
      lineHeight: _lineHeight,
      backgroundColor: _backgroundColor,
      borderColor: _borderWidth > 0 ? _borderColor : null,
      borderWidth: _borderWidth,
      opacity: _opacity,
      padding: _padding,
      margin: _margin,
      skewX: _skewX,
      skewY: _skewY,
      horizontalAlignment: _horizontalAlignment,
      verticalAlignment: _verticalAlignment,
      flipHorizontally: _flipHorizontally,
      flipVertically: _flipVertically,
      // Keep existing properties
      style: widget.item.content?.style,
      strutStyle: widget.item.content?.strutStyle,
      textAlign: _horizontalAlignment,
      textDirection: widget.item.content?.textDirection,
      locale: widget.item.content?.locale,
      softWrap: widget.item.content?.softWrap,
      overflow: widget.item.content?.overflow,
      textScaleFactor: widget.item.content?.textScaleFactor,
      maxLines: widget.item.content?.maxLines,
      semanticsLabel: widget.item.content?.semanticsLabel,
      textWidthBasis: widget.item.content?.textWidthBasis,
      textHeightBehavior: widget.item.content?.textHeightBehavior,
      selectionColor: widget.item.content?.selectionColor,
    );

    widget.onSave(content);
    Navigator.pop(context);
  }
}

class _BackgroundEditorDialog extends StatefulWidget {
  final Color backgroundColor;
  final Gradient? backgroundGradient;
  final File? backgroundImage;
  final double backgroundWidth;
  final double backgroundHeight;
  final BoxFit backgroundFit;
  final bool useGradient;
  final bool useImage;
  final Function(Color, Gradient?, File?, double, double, BoxFit, bool, bool) onSave;

  const _BackgroundEditorDialog({
    Key? key,
    required this.backgroundColor,
    this.backgroundGradient,
    this.backgroundImage,
    required this.backgroundWidth,
    required this.backgroundHeight,
    required this.backgroundFit,
    required this.useGradient,
    required this.useImage,
    required this.onSave,
  }) : super(key: key);

  @override
  _BackgroundEditorDialogState createState() => _BackgroundEditorDialogState();
}

class _BackgroundEditorDialogState extends State<_BackgroundEditorDialog> {
  late Color _selectedColor;
  late Gradient? _selectedGradient;
  late File? _selectedImage;
  late double _width;
  late double _height;
  late BoxFit _fit;
  late bool _useGradient;
  late bool _useImage;
  int _selectedTab = 0;

  @override
  void initState() {
    super.initState();
    _selectedColor = widget.backgroundColor;
    _selectedGradient = widget.backgroundGradient;
    _selectedImage = widget.backgroundImage;
    _width = widget.backgroundWidth;
    _height = widget.backgroundHeight;
    _fit = widget.backgroundFit;
    _useGradient = widget.useGradient;
    _useImage = widget.useImage;
  }

  @override
  Widget build(BuildContext context) {
    return Dialog(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: Container(
        width: MediaQuery.of(context).size.width * 0.9,
        height: MediaQuery.of(context).size.height * 0.8,
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            // Header
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                const Text(
                  'Background Editor',
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                ),
                const Spacer(),
                IconButton(
                  onPressed: () => Navigator.pop(context),
                  icon: const Icon(Icons.close),
                ),
              ],
            ),
            const Divider(),

            // Tab Bar
            Container(
              decoration: BoxDecoration(
                color: Colors.grey[200],
                borderRadius: BorderRadius.circular(8),
              ),
              child: Row(
                children: [
                  _buildTabButton('Color', 0, Icons.color_lens),
                  _buildTabButton('Gradient', 1, Icons.gradient),
                  _buildTabButton('Image', 2, Icons.image),
                  _buildTabButton('Size', 3, Icons.aspect_ratio),
                ],
              ),
            ),
            const SizedBox(height: 10),

            // Content
            Expanded(
              child: _buildTabContent(),
            ),

            // Buttons
            const SizedBox(height: 10),
            Row(
              children: [
                Expanded(
                  child: OutlinedButton(
                    onPressed: () => Navigator.pop(context),
                    child: const Text('Cancel'),
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: ElevatedButton(
                    onPressed: _saveSettings,
                    child: const Text('Apply'),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildTabButton(String title, int index, IconData icon) {
    final isSelected = _selectedTab == index;
    return Expanded(
      child: GestureDetector(
        onTap: () => setState(() => _selectedTab = index),
        child: Container(
          padding: const EdgeInsets.symmetric(vertical: 12),
          decoration: BoxDecoration(
            color: isSelected ? Colors.blue : Colors.transparent,
            borderRadius: BorderRadius.circular(8),
          ),
          child: Column(
            children: [
              Icon(
                icon,
                color: isSelected ? Colors.white : Colors.grey[600],
                size: 20,
              ),
              const SizedBox(height: 4),
              Text(
                title,
                style: TextStyle(
                  color: isSelected ? Colors.white : Colors.grey[600],
                  fontSize: 12,
                  fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildTabContent() {
    switch (_selectedTab) {
      case 0:
        return _buildColorTab();
      case 1:
        return _buildGradientTab();
      case 2:
        return _buildImageTab();
      case 3:
        return _buildSizeTab();
      default:
        return const SizedBox();
    }
  }

  Widget _buildColorTab() {
    return Column(
      children: [
        // Color Preview
        Container(
          width: double.infinity,
          height: 100,
          decoration: BoxDecoration(
            color: _selectedColor,
            borderRadius: BorderRadius.circular(8),
            border: Border.all(color: Colors.grey[300]!),
          ),
        ),
        const SizedBox(height: 20),
        
        // Predefined Colors
        const Text('Quick Colors', style: TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(height: 10),
        Wrap(
          spacing: 8,
          runSpacing: 8,
          children: [
            Colors.white,
            Colors.black,
            Colors.red,
            Colors.green,
            Colors.blue,
            Colors.yellow,
            Colors.purple,
            Colors.orange,
            Colors.pink,
            Colors.teal,
            Colors.grey,
            Colors.brown,
          ].map((color) => GestureDetector(
            onTap: () => setState(() {
              _selectedColor = color;
              _useGradient = false;
              _useImage = false;
            }),
            child: Container(
              width: 40,
              height: 40,
              decoration: BoxDecoration(
                color: color,
                borderRadius: BorderRadius.circular(8),
                border: Border.all(
                  color: _selectedColor == color ? Colors.blue : Colors.grey[300]!,
                  width: _selectedColor == color ? 3 : 1,
                ),
              ),
            ),
          )).toList(),
        ),
      ],
    );
  }

  Widget _buildGradientTab() {
    final gradients = [
      const LinearGradient(colors: [Colors.blue, Colors.purple]),
      const LinearGradient(colors: [Colors.orange, Colors.red]),
      const LinearGradient(colors: [Colors.green, Colors.teal]),
      const LinearGradient(colors: [Colors.pink, Colors.purple]),
      const LinearGradient(colors: [Colors.yellow, Colors.orange]),
      const LinearGradient(colors: [Colors.indigo, Colors.blue]),
    ];

    return Column(
      children: [
        const Text('Gradient Presets', style: TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(height: 10),
        Expanded(
          child: GridView.builder(
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              crossAxisSpacing: 12,
              mainAxisSpacing: 12,
              childAspectRatio: 2,
            ),
            itemCount: gradients.length,
            itemBuilder: (context, index) {
              final gradient = gradients[index];
              return GestureDetector(
                onTap: () => setState(() {
                  _selectedGradient = gradient;
                  _useGradient = true;
                  _useImage = false;
                }),
                child: Container(
                  decoration: BoxDecoration(
                    gradient: gradient,
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(
                      color: _selectedGradient == gradient ? Colors.blue : Colors.grey[300]!,
                      width: _selectedGradient == gradient ? 3 : 1,
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }

  Widget _buildImageTab() {
    return Column(
      children: [
        // Image Preview
        if (_selectedImage != null)
          Container(
            width: double.infinity,
            height: 150,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: Colors.grey[300]!),
              image: DecorationImage(
                image: FileImage(_selectedImage!),
                fit: _fit,
              ),
            ),
          )
        else
          Container(
            width: double.infinity,
            height: 150,
            decoration: BoxDecoration(
              color: Colors.grey[200],
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: Colors.grey[300]!),
            ),
            child: const Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(Icons.image, size: 48, color: Colors.grey),
                Text('No image selected', style: TextStyle(color: Colors.grey)),
              ],
            ),
          ),
        const SizedBox(height: 20),

        // Buttons
        Row(
          children: [
            Expanded(
              child: ElevatedButton.icon(
                onPressed: _pickImageFromGallery,
                icon: const Icon(Icons.photo_library),
                label: const Text('Gallery', style: TextStyle(fontSize: 14)),
              ),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: ElevatedButton.icon(
                onPressed: _pickImageFromCamera,
                icon: const Icon(Icons.camera_alt),
                label: const Text('Camera', style: TextStyle(fontSize: 13)),
              ),
            ),
          ],
        ),
        
        if (_selectedImage != null) ...[
          const SizedBox(height: 12),
          Row(
            children: [
              Expanded(
                child: OutlinedButton.icon(
                  onPressed: _cropImage,
                  icon: const Icon(Icons.crop),
                  label: const Text('Crop'),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: OutlinedButton.icon(
                  onPressed: () => setState(() => _selectedImage = null),
                  icon: const Icon(Icons.delete),
                  label: const Text('Remove', style: TextStyle(fontSize: 13)),
                ),
              ),
            ],
          ),
        ],

        const SizedBox(height: 20),
        const Text('Image Fit', style: TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(height: 10),
        DropdownButtonFormField<BoxFit>(
          value: _fit,
          decoration: const InputDecoration(
            border: OutlineInputBorder(),
            contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
          ),
          items: BoxFit.values.map((fit) {
            return DropdownMenuItem(
              value: fit,
              child: Text(fit.toString().split('.').last),
            );
          }).toList(),
          onChanged: (value) => setState(() => _fit = value!),
        ),
      ],
    );
  }

  Widget _buildSizeTab() {
    return Column(
      children: [
        // Size Preview
        Container(
          width: double.infinity,
          height: 100,
          decoration: BoxDecoration(
            color: Colors.grey[100],
            borderRadius: BorderRadius.circular(8),
            border: Border.all(color: Colors.grey[300]!),
          ),
          child: Center(
            child: Text(
              '${_width.toInt()} × ${_height.toInt()}',
              style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
          ),
        ),
        const SizedBox(height: 10),

        // Width Slider
        const Text('Width', style: TextStyle(fontWeight: FontWeight.bold)),
        Slider(
          value: _width,
          min: 300,
          max: 2000,
          divisions: 34,
          label: _width.toInt().toString(),
          onChanged: (value) => setState(() => _width = value),
        ),
        const SizedBox(height: 10),

        // Height Slider
        const Text('Height', style: TextStyle(fontWeight: FontWeight.bold)),
        Slider(
          value: _height,
          min: 300,
          max: 2000,
          divisions: 34,
          label: _height.toInt().toString(),
          onChanged: (value) => setState(() => _height = value),
        ),
        const SizedBox(height: 5),

        // Preset Sizes
        const Text('Preset Sizes', style: TextStyle(fontWeight: FontWeight.bold)),
        Wrap(
          spacing: 5,
          runSpacing: 5,

          children: [
            {'name': 'HD', 'width': 1280.0, 'height': 720.0},
            {'name': 'Full HD', 'width': 1920.0, 'height': 1080.0},
            {'name': 'Square', 'width': 800.0, 'height': 800.0},
            {'name': 'A4', 'width': 794.0, 'height': 1123.0},
          ].map((preset) => ElevatedButton(
            onPressed: () => setState(() {
              _width = preset['width'] as double;
              _height = preset['height'] as double;
            }),
            child: Text(preset['name'] as String),
          )).toList(),
        ),
      ],
    );
  }

  Future<void> _pickImageFromGallery() async {
    final ImagePicker picker = ImagePicker();
    final XFile? image = await picker.pickImage(source: ImageSource.gallery);
    
    if (image != null) {
      setState(() {
        _selectedImage = File(image.path);
        _useImage = true;
        _useGradient = false;
      });
    }
  }

  Future<void> _pickImageFromCamera() async {
    final ImagePicker picker = ImagePicker();
    final XFile? image = await picker.pickImage(source: ImageSource.camera);
    
    if (image != null) {
      setState(() {
        _selectedImage = File(image.path);
        _useImage = true;
        _useGradient = false;
      });
    }
  }

  Future<void> _cropImage() async {
    if (_selectedImage == null) return;

    // For now, we'll just show a message since image_cropper might not be installed
    // In a real implementation, you would use image_cropper package
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Crop functionality requires image_cropper package'),
      ),
    );

    /* 
    Uncomment this when image_cropper is properly installed:
    
    final croppedFile = await ImageCropper().cropImage(
      sourcePath: _selectedImage!.path,
      uiSettings: [
        AndroidUiSettings(
          toolbarTitle: 'Crop Background',
          toolbarColor: Colors.blue,
          toolbarWidgetColor: Colors.white,
        ),
        IOSUiSettings(
          title: 'Crop Background',
        ),
      ],
    );

    if (croppedFile != null) {
      setState(() {
        _selectedImage = File(croppedFile.path);
      });
    }
    */
  }

  void _saveSettings() {
    widget.onSave(
      _selectedColor,
      _selectedGradient,
      _selectedImage,
      _width,
      _height,
      _fit,
      _useGradient,
      _useImage,
    );
    Navigator.pop(context);
  }
}
8
likes
0
points
22
downloads

Publisher

unverified uploader

Weekly Downloads

An advanced, stable tool for building photo editing and canvas-based apps with layers, transformations, and interactive elements.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_svg, shimmer, vector_math

More

Packages that depend on stack_board_plus