Skip to main content

strategy_forge 0.1.0 copy "strategy_forge: ^0.1.0" to clipboard
strategy_forge: ^0.1.0 copied to clipboard

An embeddable Flutter strategy editor with configurable layouts, drawing tools, markers, JSON persistence, and PNG export.

example/lib/main.dart

import 'package:strategy_forge/strategy_forge.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:file_saver/file_saver.dart';

import 'demo_configurations.dart';

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

class StrategyForgeDemo extends StatelessWidget {
  const StrategyForgeDemo({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'Strategy Forge',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(
            seedColor: const Color(0xff0b5b8c),
            surface: const Color(0xfffbfcfe),
          ),
          scaffoldBackgroundColor: const Color(0xffeef3f7),
          useMaterial3: true,
          appBarTheme: const AppBarTheme(
            backgroundColor: Color(0xff0d2a45),
            foregroundColor: Colors.white,
            elevation: 0,
            scrolledUnderElevation: 0,
            toolbarHeight: 68,
            titleSpacing: 16,
            surfaceTintColor: Colors.transparent,
          ),
          popupMenuTheme: PopupMenuThemeData(
            elevation: 8,
            color: const Color(0xffffffff),
            surfaceTintColor: Colors.transparent,
            menuPadding: const EdgeInsets.symmetric(vertical: 8),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(16),
              side: const BorderSide(color: Color(0xffd9e2e9)),
            ),
          ),
          dialogTheme: DialogThemeData(
            backgroundColor: const Color(0xffffffff),
            surfaceTintColor: Colors.transparent,
            shape:
                RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
          ),
          tooltipTheme: TooltipThemeData(
            waitDuration: const Duration(milliseconds: 450),
            decoration: BoxDecoration(
              color: const Color(0xff17364f),
              borderRadius: BorderRadius.circular(8),
            ),
            textStyle: const TextStyle(color: Colors.white, fontSize: 12),
          ),
          snackBarTheme: SnackBarThemeData(
            behavior: SnackBarBehavior.floating,
            shape:
                RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
            insetPadding: const EdgeInsets.all(16),
          ),
          iconButtonTheme: IconButtonThemeData(
            style: IconButton.styleFrom(
              minimumSize: const Size.square(48),
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
            ),
          ),
        ),
        home: const DemoHome(),
      );
}

class DemoHome extends StatefulWidget {
  const DemoHome({
    super.key,
    this.initialDownloadEnabled = true,
    this.strategyJson,
    this.onStrategyJsonChanged,
    this.onStrategyImageGenerated,
    this.savePng,
  });

  final bool initialDownloadEnabled;

  /// Saved plugin data to restore. A later changed value is loaded as well.
  final String? strategyJson;

  /// Receives the current plugin document as JSON whenever it changes.
  final ValueChanged<String>? onStrategyJsonChanged;

  /// Receives the PNG bytes generated by the plugin before file download.
  final ValueChanged<Uint8List>? onStrategyImageGenerated;

  final Future<bool> Function(Uint8List bytes)? savePng;

  @override
  State<DemoHome> createState() => _DemoHomeState();
}

class _DemoHomeState extends State<DemoHome> {
  late final StrategyEditorController _controller;
  late bool _downloadEnabled;
  bool _saving = false;
  String? _lastEmittedJson;

  @override
  void initState() {
    super.initState();
    _controller = StrategyEditorController(config: DemoConfigurations.sample());
    if (widget.strategyJson case final json?) {
      _controller.loadJson(json);
    }
    _controller.addListener(_emitJsonIfChanged);
    _downloadEnabled = widget.initialDownloadEnabled;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted) _emitJsonIfChanged();
    });
  }

  @override
  void didUpdateWidget(covariant DemoHome oldWidget) {
    super.didUpdateWidget(oldWidget);
    final json = widget.strategyJson;
    if (json != null && json != oldWidget.strategyJson) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted && json != _controller.exportJson()) {
          _controller.loadJson(json);
        }
      });
    }
    if (widget.onStrategyJsonChanged != oldWidget.onStrategyJsonChanged) {
      _lastEmittedJson = null;
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) _emitJsonIfChanged();
      });
    }
  }

  @override
  void dispose() {
    _controller.removeListener(_emitJsonIfChanged);
    _controller.dispose();
    super.dispose();
  }

  void _emitJsonIfChanged() {
    final json = _controller.exportJson();
    if (json == _lastEmittedJson) return;
    _lastEmittedJson = json;
    widget.onStrategyJsonChanged?.call(json);
  }

  Future<void> _export() async {
    if (!_downloadEnabled || _saving) return;
    setState(() => _saving = true);
    final messenger = ScaffoldMessenger.of(context);
    try {
      final bytes = await _controller.exportPng();
      if (!mounted || !_downloadEnabled) return;
      widget.onStrategyImageGenerated?.call(bytes);
      final saved = await (widget.savePng?.call(bytes) ?? _savePng(bytes));
      if (!mounted) return;
      messenger.showSnackBar(
        SnackBar(
          content: Text(saved ? 'PNG saved' : 'PNG save cancelled or failed'),
        ),
      );
    } catch (error) {
      if (!mounted) return;
      messenger.showSnackBar(
        SnackBar(content: Text('Could not save PNG: $error')),
      );
    } finally {
      if (mounted) setState(() => _saving = false);
    }
  }

  Future<bool> _savePng(Uint8List bytes) async {
    final fileName = 'strategy_${DateTime.now().millisecondsSinceEpoch}';
    final result = kIsWeb
        ? await FileSaver.instance.saveFile(
            name: fileName,
            bytes: bytes,
            fileExtension: 'png',
            mimeType: MimeType.png,
          )
        : await FileSaver.instance.saveAs(
            name: fileName,
            bytes: bytes,
            fileExtension: 'png',
            mimeType: MimeType.png,
          );
    return result != null && result.isNotEmpty;
  }

  Widget _buildTitle(BuildContext context, {required bool showSubtitle}) {
    return Row(
      children: [
        Container(
          width: 38,
          height: 38,
          decoration: BoxDecoration(
            color: Colors.white.withValues(alpha: .12),
            borderRadius: BorderRadius.circular(11),
            border: Border.all(color: Colors.white24),
          ),
          child: const Icon(Icons.dashboard_customize_outlined, size: 22),
        ),
        const SizedBox(width: 11),
        Expanded(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Strategy Forge',
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  fontWeight: FontWeight.w700,
                  letterSpacing: -0.2,
                ),
              ),
              if (showSubtitle)
                Text(
                  'Strategy editor demo',
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: Theme.of(context).textTheme.labelSmall?.copyWith(
                        color: Colors.white70,
                        letterSpacing: 0.4,
                      ),
                ),
            ],
          ),
        ),
      ],
    );
  }

  ButtonStyle _downloadButtonStyle() => IconButton.styleFrom(
        backgroundColor: Colors.white.withValues(alpha: .12),
        disabledBackgroundColor: Colors.white.withValues(alpha: .05),
        foregroundColor: Colors.white,
        disabledForegroundColor: Colors.white38,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
          side: const BorderSide(color: Colors.white24),
        ),
      );

  @override
  Widget build(BuildContext context) => LayoutBuilder(
        builder: (context, constraints) {
          final shortViewport = constraints.maxHeight < 600;
          return Scaffold(
            appBar: AppBar(
              toolbarHeight: shortViewport ? 56 : 68,
              title: _buildTitle(
                context,
                showSubtitle: constraints.maxWidth >= 520 && !shortViewport,
              ),
              actions: [
                IconButton(
                  key: const ValueKey('demo-download'),
                  tooltip: _downloadEnabled
                      ? 'Download strategy as PNG'
                      : 'PNG downloads are disabled',
                  onPressed: _downloadEnabled && !_saving ? _export : null,
                  constraints:
                      const BoxConstraints.tightFor(width: 48, height: 48),
                  style: _downloadButtonStyle(),
                  icon: _saving
                      ? const SizedBox.square(
                          dimension: 20,
                          child: CircularProgressIndicator(
                            strokeWidth: 2,
                            color: Colors.white,
                          ),
                        )
                      : const Icon(Icons.download_rounded),
                ),
                const SizedBox(width: 4),
              ],
            ),
            body: SafeArea(child: StrategyEditor(controller: _controller)),
          );
        },
      );
}
1
likes
0
points
468
downloads

Documentation

Documentation

Publisher

verified publishermarlenfranto.com

Weekly Downloads

An embeddable Flutter strategy editor with configurable layouts, drawing tools, markers, JSON persistence, and PNG export.

Homepage
Repository (GitHub)
View/report issues

Topics

#flutter #strategy #drawing #diagram #editor

License

unknown (license)

Dependencies

flutter, flutter_svg

More

Packages that depend on strategy_forge