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

High-performance PERT/CPM chart for Flutter. Visualize complex dependencies with automated network diagrams.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:legacy_pert_chart/legacy_pert_chart.dart';
import 'package:legacy_gantt_chart/legacy_gantt_chart.dart';
import 'package:provider/provider.dart';
import 'pert_view_model.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => PertViewModel(),
      child: MaterialApp(
        title: 'Legacy PERT Chart Demo',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        darkTheme: ThemeData.dark(useMaterial3: true),
        themeMode: ThemeMode.system,
        home: const PertChartPage(),
      ),
    );
  }
}

class PertChartPage extends StatefulWidget {
  const PertChartPage({super.key});

  @override
  State<PertChartPage> createState() => _PertChartPageState();
}

class _PertChartPageState extends State<PertChartPage> {
  final TextEditingController _uriController = TextEditingController(
    text: 'https://api.gantt-sync.com',
  );
  final TextEditingController _tenantController = TextEditingController(
    text: 'legacy',
  );
  final TextEditingController _userController = TextEditingController(
    text: 'patrick',
  );
  final TextEditingController _passController = TextEditingController(
    text: 'password',
  );

  bool _isPanelVisible = true;

  double _controlPanelWidth = 350.0;

  @override
  Widget build(BuildContext context) {
    final viewModel = context.watch<PertViewModel>();
    final tasks = viewModel.tasks;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Legacy PERT Chart Example'),
        leading: IconButton(
          icon: const Icon(Icons.menu),
          onPressed: () => setState(() => _isPanelVisible = !_isPanelVisible),
        ),
      ),
      body: Row(
        children: [
          if (_isPanelVisible)
            SizedBox(
              width: _controlPanelWidth,
              child: _buildControlPanel(context, viewModel),
            ),
          if (_isPanelVisible)
            GestureDetector(
              onHorizontalDragUpdate: (details) {
                setState(() {
                  _controlPanelWidth = (_controlPanelWidth + details.delta.dx)
                      .clamp(200.0, 600.0);
                });
              },
              child: MouseRegion(
                cursor: SystemMouseCursors.resizeLeftRight,
                child: VerticalDivider(
                  width: 8,
                  thickness: 1,
                  color: Theme.of(context).dividerColor,
                ),
              ),
            ),
          Expanded(
            child: viewModel.isConnected && tasks.isEmpty
                ? const Center(child: Text("Waiting for data..."))
                : LegacyPertChartWidget(
                    tasks: tasks.isEmpty ? _getSampleTasks() : tasks,
                    onDependencyAdded: (fromId, toId) {
                      if (tasks.isEmpty) {
                        ScaffoldMessenger.of(context).showSnackBar(
                          const SnackBar(
                            content: Text(
                              'Cannot modify sample data. Please connect to server.',
                            ),
                          ),
                        );
                        return;
                      }
                      final fromName = tasks
                          .firstWhere((t) => t.id == fromId)
                          .name;
                      final toName = tasks.firstWhere((t) => t.id == toId).name;

                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          content: Text(
                            'Creating dependency: $fromName -> $toName',
                          ),
                        ),
                      );

                      viewModel.addDependency(fromId, toId);
                    },
                    onNodeTap: (task) {
                      if (tasks.isEmpty) {
                        ScaffoldMessenger.of(context).showSnackBar(
                          const SnackBar(
                            content: Text(
                              'Cannot edit sample data. Please connect to server.',
                            ),
                          ),
                        );
                        return;
                      }
                      _showEditTaskDialog(context, task, viewModel);
                    },
                    nodeBuilder: (context, task, size) {
                      if (task.name == 'Custom Node') {
                        return Container(
                          decoration: BoxDecoration(
                            color: Colors.amber,
                            borderRadius: BorderRadius.circular(20),
                            boxShadow: [
                              BoxShadow(
                                color: Colors.black26,
                                blurRadius: 4,
                                offset: const Offset(2, 2),
                              ),
                            ],
                          ),
                          alignment: Alignment.center,
                          child: const Icon(Icons.star, color: Colors.white),
                        );
                      }
                      return null;
                    },
                  ),
          ),
        ],
      ),
    );
  }

  Widget _buildControlPanel(BuildContext context, PertViewModel viewModel) {
    return Container(
      color: Theme.of(context).cardColor,
      child: ListView(
        padding: const EdgeInsets.all(12.0),
        children: [
          Text('Server Sync', style: Theme.of(context).textTheme.titleLarge),
          const SizedBox(height: 16),
          if (viewModel.isConnected)
            Container(
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: Colors.green.withValues(alpha: 0.1),
                border: Border.all(color: Colors.green),
                borderRadius: BorderRadius.circular(4),
              ),
              child: Column(
                children: [
                  const Row(
                    children: [
                      Icon(Icons.check_circle, color: Colors.green, size: 16),
                      SizedBox(width: 8),
                      Text(
                        'Connected',
                        style: TextStyle(
                          color: Colors.green,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  ),
                  StreamBuilder<int>(
                    stream: viewModel.outboundPendingCount,
                    builder: (context, snapshot) {
                      final count = snapshot.data ?? 0;
                      if (count == 0) return const SizedBox.shrink();
                      return Padding(
                        padding: const EdgeInsets.only(top: 8.0),
                        child: Row(
                          children: [
                            const Icon(
                              Icons.upload_file,
                              size: 16,
                              color: Colors.orange,
                            ),
                            const SizedBox(width: 8),
                            Text(
                              'Pending Outbound: $count',
                              style: const TextStyle(
                                color: Colors.orange,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ],
                        ),
                      );
                    },
                  ),
                  StreamBuilder<SyncProgress>(
                    stream: viewModel.inboundProgress,
                    builder: (context, snapshot) {
                      final progress = snapshot.data;
                      if (progress == null ||
                          progress.total == 0 ||
                          (progress.processed >= progress.total &&
                              progress.total > 0)) {
                        return const SizedBox.shrink();
                      }
                      return Padding(
                        padding: const EdgeInsets.only(top: 8.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.stretch,
                          children: [
                            Text(
                              'Syncing: ${progress.processed} / ${progress.total}',
                              style: const TextStyle(
                                fontSize: 12,
                                color: Colors.blue,
                              ),
                            ),
                            const SizedBox(height: 4),
                            LinearProgressIndicator(value: progress.percentage),
                          ],
                        ),
                      );
                    },
                  ),
                  const SizedBox(height: 8),
                  ElevatedButton(
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.red,
                      foregroundColor: Colors.white,
                    ),
                    onPressed: () => viewModel.disconnect(),
                    child: const Text('Disconnect'),
                  ),
                ],
              ),
            )
          else
            Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                TextField(
                  controller: _uriController,
                  decoration: const InputDecoration(
                    labelText: 'Server URI',
                    isDense: true,
                  ),
                ),
                const SizedBox(height: 8),
                TextField(
                  controller: _tenantController,
                  decoration: const InputDecoration(
                    labelText: 'Tenant ID',
                    isDense: true,
                  ),
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _userController,
                        decoration: const InputDecoration(
                          labelText: 'User',
                          isDense: true,
                        ),
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: TextField(
                        controller: _passController,
                        decoration: const InputDecoration(
                          labelText: 'Pass',
                          isDense: true,
                        ),
                        obscureText: true,
                      ),
                    ),
                  ],
                ),
                if (viewModel.error != null)
                  Padding(
                    padding: const EdgeInsets.symmetric(vertical: 8.0),
                    child: Text(
                      viewModel.error!,
                      style: const TextStyle(color: Colors.red),
                    ),
                  ),
                const SizedBox(height: 12),
                ElevatedButton(
                  onPressed: () {
                    viewModel.connect(
                      uri: _uriController.text,
                      tenantId: _tenantController.text,
                      username: _userController.text,
                      password: _passController.text,
                    );
                  },
                  child: const Text('Connect'),
                ),
              ],
            ),
        ],
      ),
    );
  }

  List<LegacyPertTask> _getSampleTasks() {
    return [
      const LegacyPertTask(id: '1', name: 'Start'),
      const LegacyPertTask(id: '2', name: 'Task A', dependencyIds: ['1']),
      const LegacyPertTask(id: '3', name: 'Task B', dependencyIds: ['1']),
      const LegacyPertTask(
        id: '4',
        name: 'Custom Node',
        dependencyIds: ['2', '3'],
      ),
      const LegacyPertTask(id: '5', name: 'End', dependencyIds: ['4']),
    ];
  }

  void _showEditTaskDialog(
    BuildContext context,
    LegacyPertTask task,
    PertViewModel viewModel,
  ) {
    final durationController = TextEditingController(
      text: task.duration?.inHours.toString() ?? '0',
    );

    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text('Edit Task: ${task.name}'),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              TextField(
                controller: durationController,
                decoration: const InputDecoration(
                  labelText: 'Duration (Hours)',
                  suffixText: 'h',
                ),
                keyboardType: TextInputType.number,
              ),
            ],
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('Cancel'),
            ),
            ElevatedButton(
              onPressed: () {
                final hours = int.tryParse(durationController.text);
                if (hours != null) {
                  viewModel.updateTaskDuration(task.id, Duration(hours: hours));
                }
                Navigator.of(context).pop();
              },
              child: const Text('Save'),
            ),
          ],
        );
      },
    );
  }
}
3
likes
160
points
148
downloads

Publisher

verified publishergantt-sync.com

Weekly Downloads

High-performance PERT/CPM chart for Flutter. Visualize complex dependencies with automated network diagrams.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, legacy_gantt_chart, legacy_gantt_protocol

More

Packages that depend on legacy_pert_chart