runFlutsimPreview function

Future<void> runFlutsimPreview()

Implementation

Future<void> runFlutsimPreview() async {
  final port = FlutsimConfig.port;
  final buildDir = Directory('build/web');
  final liveReload = LiveReloadServer();
  final hotReload = HotReloadServer();
  final autoReload = AutoReloadServer();
  final instantUI = InstantUIServer();

  await liveReload.start();

  // Start hot reload server if enabled
  if (FlutsimConfig.enableInstantHotReload) {
    await hotReload.start();
  }

  // Start auto reload server if enabled
  if (FlutsimConfig.enableAutoReload) {
    await autoReload.start();
  }

  // Start instant UI update server if enabled
  if (FlutsimConfig.enableInstantUIUpdates) {
    await instantUI.start();
  }

  // Check if we should use fast development mode
  if (FlutsimConfig.useFastMode) {
    print('πŸš€ Starting Flutter in fast development mode...');
    await _startFastDevelopmentMode(
        port, liveReload, hotReload, autoReload, instantUI);
    return;
  }

  // Step 1: Build Flutter Web with development optimizations
  if (!buildDir.existsSync()) {
    print('πŸ”§ Building Flutter web (development mode)...');

    // Get the Flutter path to use full path instead of relying on PATH
    final flutterPath = getFlutterPath();
    final flutterCommand = flutterPath ?? 'flutter';

    // Build flags based on configuration
    final buildFlags = ['build', 'web', '--release'];

    if (FlutsimConfig.useHtmlRenderer) {
      buildFlags.addAll(['--web-renderer', 'html']);
    }

    if (FlutsimConfig.disableSkia) {
      buildFlags.add('--dart-define=FLUTTER_WEB_USE_SKIA=false');
    }

    if (FlutsimConfig.disableIconTreeShaking) {
      buildFlags.add('--no-tree-shake-icons');
    }

    // Use development-optimized build flags
    final buildProcess = await Process.start(
      flutterCommand,
      buildFlags,
      mode: ProcessStartMode.inheritStdio,
    );
    final exitCode = await buildProcess.exitCode;

    if (exitCode != 0) {
      print('❌ Web build failed.');
      return;
    }
  } else {
    print('βœ… Web build already exists.');
  }

  // Step 2: Get IP address and show available interfaces
  final ip = await getLocalIp();
  final url = 'http://$ip:$port';

  // Show all available interfaces for debugging
  await _showAvailableInterfaces();

  // Step 3: Serve Web Folder with live reload injection
  final handler = createStaticHandler(
    path.absolute(buildDir.path),
    defaultDocument: 'index.html',
  );

  // Create a middleware to inject live reload script
  Future<Response> liveReloadHandler(Request request) async {
    final response = await handler(request);

    // Only inject script for HTML files
    if (request.url.path.endsWith('.html') || request.url.path.isEmpty) {
      final body = await response.readAsString();

      // Inject live reload script before closing body tag
      final liveReloadScript = '''
<script>
// Live Reload Client
(function() {
  const ws = new WebSocket('ws://' + window.location.hostname + ':${FlutsimConfig.liveReloadPort}');

  ws.onopen = function() {
    console.log('πŸ”Œ Connected to live reload server');
  };

  ws.onmessage = function(event) {
    const data = JSON.parse(event.data);
    if (data.type === 'reload') {
      console.log('πŸ”„ Live reload triggered');
      window.location.reload();
    }
  };

  ws.onerror = function(error) {
    console.log('❌ WebSocket error:', error);
  };

  ws.onclose = function() {
    console.log('πŸ”Œ Disconnected from live reload server');
    // Try to reconnect after 2 seconds
    setTimeout(function() {
      window.location.reload();
    }, 2000);
  };
})();
</script>
''';

      // Inject hot reload script if enabled
      String modifiedBody =
          body.replaceFirst('</body>', '$liveReloadScript</body>');

      if (FlutsimConfig.enableInstantHotReload) {
        modifiedBody = HotReloadManager.injectHotReloadScript(modifiedBody);
      }

      if (FlutsimConfig.enableAutoReload) {
        modifiedBody = BrowserAutoReload.injectAutoReloadScript(modifiedBody);
      }

      if (FlutsimConfig.enableInstantUIUpdates) {
        modifiedBody = InstantUIUpdater.injectInstantUpdateScript(modifiedBody);
      }

      return Response.ok(
        modifiedBody,
        headers: response.headers,
      );
    }

    return response;
  }

  await shelf_io.serve(liveReloadHandler, InternetAddress.anyIPv4, port);

  print('βœ… Local server started at: $url');
  print('\nπŸ“± Open this URL on your device: $url');
  print('πŸ”„ Press Ctrl+C to stop the server');

  if (FlutsimConfig.enableInstantHotReload) {
    print('πŸ”₯ Instant hot reload enabled - changes appear immediately!');
  }

  if (FlutsimConfig.enableAutoReload) {
    print('πŸ”„ Auto reload enabled - browser will refresh automatically!');
  }

  if (FlutsimConfig.enableInstantUIUpdates) {
    print('⚑ Instant UI updates enabled - DOM changes without reload!');
  }

  // Generate and display QR code
  await generateAndDisplayQRCode(url);

  // Get the Flutter path
  final flutterPath = getFlutterPath();
  final flutterCommand = flutterPath ?? 'flutter';

  // Start Flutter in development mode with web-server and hot reload
  print('πŸš€ Starting Flutter development server with hot reload...');

  // Start Flutter process with stdin/stdout communication
  final flutterProcess = await Process.start(
    flutterCommand,
    [
      'run',
      '-d', 'web-server',
      '--web-port', port.toString(),
      '--web-hostname', '0.0.0.0',
      '--hot', // Enable hot reload
      '--debug', // Use debug mode for faster hot reload
    ],
    mode: ProcessStartMode.normal,
  );

  // Pipe stdout and stderr to this process
  stdout.addStream(flutterProcess.stdout);
  stderr.addStream(flutterProcess.stderr);

  // Start file watcher for automatic hot reload
  final watcher = DirectoryWatcher('lib');
  print('πŸ‘€ Watching lib/ for changes...');

  Timer? debounceTimer;
  bool isHotReloading = false;

  watcher.events.listen((event) async {
    // Skip if already hot reloading
    if (isHotReloading) {
      print('⏳ Hot reload already in progress, skipping...');
      return;
    }

    // Debounce rapid changes
    debounceTimer?.cancel();
    debounceTimer =
        Timer(Duration(milliseconds: FlutsimConfig.debounceDelay), () async {
      print('πŸ”ƒ Change detected in ${event.path}');
      print('πŸ”₯ Triggering automatic hot reload...');

      isHotReloading = true;
      try {
        // Trigger instant hot reload if enabled
        if (FlutsimConfig.enableInstantHotReload) {
          hotReload.triggerInstantHotReload();
        }

        // Trigger auto reload if enabled
        if (FlutsimConfig.enableAutoReload) {
          autoReload.triggerAutoReload();
        }

        // Trigger instant UI updates if enabled
        if (FlutsimConfig.enableInstantUIUpdates) {
          instantUI.sendInstantUIUpdate([
            {
              'action': 'update_text',
              'selector': 'body',
              'text': 'Updated at ${DateTime.now().toString()}',
            }
          ]);
        }

        // Send 'r' to flutter process stdin
        flutterProcess.stdin.write('r\n');
        print('βœ… Hot reload triggered automatically!');
      } catch (e) {
        print('❌ Failed to trigger hot reload: $e');
      } finally {
        isHotReloading = false;
      }
    });
  });

  // Wait for the process to complete
  await flutterProcess.exitCode;
}