runFlutsimPreview function

Future<void> runFlutsimPreview()

Implementation

Future<void> runFlutsimPreview() async {
  final port = FlutsimConfig.port;
  final buildDir = Directory('build/web');
  final unifiedServer = UnifiedWebSocketServer();
  await unifiedServer.start();

  // Check if we should use fast development mode
  if (FlutsimConfig.useFastMode) {
    print('$ansiBlue🚀 Starting Flutter in fast development mode...$ansiReset');
    await _startFastDevelopmentMode(port, unifiedServer);
    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 unified FlutSim client script
      String modifiedBody = FlutSimClient.injectScript(body, unifiedServer.port);

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

    return response;
  }

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

  print('$ansiGreen✅ Local server started at: $url$ansiReset');
  print('\n$ansiBlue📱 Open this URL on your device: $url$ansiReset');
  print('$ansiYellow🔄 Press Ctrl+C to stop the server$ansiReset');

  // Auto-open browser for standard preview mode
  print('$ansiBlue🌐 Opening browser automatically...$ansiReset');
  openBrowser(url);

  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('$ansiBlue🚀 Starting Flutter development server with hot reload...$ansiReset');

  // 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,
  );

  String? devToolsUrl;

  // Pipe stdout and stderr to this process and intercept errors
  flutterProcess.stdout.transform(utf8.decoder).listen((data) {
    stdout.write(data);

    // Capture DevTools URL
    if (data.contains('DevTools debugger')) {
      final match = RegExp(r'http://127\.0\.0\.1:[0-9]+[^ \n\r]*').firstMatch(data);
      if (match != null) {
        devToolsUrl = match.group(0);
      }
    }

    // Check for compilation or generic errors to overlay in browser
    if (data.contains('Error: ') || data.contains('Exception: ') || data.contains('Failed to compile')) {
      unifiedServer.sendError(data);
    } else if (data.contains('Reloaded ') || data.contains('Restarted ')) {
      unifiedServer.clearError(); // Wipe the overlay on successful reload
    }
  });

  flutterProcess.stderr.transform(utf8.decoder).listen((data) {
    stderr.write(data);
    if (data.contains('Error: ') || data.contains('Exception: ')) {
      unifiedServer.sendError(data);
    }
  });

  // Start Terminal UI (TUI) Listener
  if (stdin.hasTerminal) {
    stdin.echoMode = false;
    stdin.lineMode = false;

    print('\n$ansiBlue🎮 Terminal UI active:$ansiReset');
    print('  [r] Hot Reload  |  [R] Hot Restart');
    print('  [d] DevTools    |  [c] Clear');
    print('  [q] Quit        |  [h] Help / QR');

    stdin.listen((List<int> codes) {
      final key = utf8.decode(codes);

      switch (key) {
        case 'r':
          print('\n$ansiYellow🔥 Manual Hot Reload...$ansiReset');
          flutterProcess.stdin.write('r\n');
          unifiedServer.triggerHotReload();
          break;
        case 'R':
          print('\n$ansiYellow💥 Manual Hot Restart...$ansiReset');
          flutterProcess.stdin.write('R\n');
          unifiedServer.triggerAutoReload();
          break;
        case 'd':
          if (devToolsUrl != null) {
            print('\n$ansiBlue🛠 Opening Dart DevTools...$ansiReset');
            openBrowser(devToolsUrl!);
          } else {
            print('\n$ansiYellow⚠️ DevTools URL not yet available.$ansiReset');
          }
          break;
        case 'c':
          print('\x1B[2J\x1B[0;0H'); // Clear terminal
          break;
        case 'q':
        case '\x03': // Ctrl+C
          print('\n$ansiGreen👋 Exiting FlutSim...$ansiReset');
          flutterProcess.kill();
          exit(0);
        case 'h':
          generateAndDisplayQRCode(url);
          break;
      }
    });
  }

  // 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) {
          unifiedServer.triggerHotReload();
        }

        // Trigger auto reload if enabled and instant hot reload is disabled
        if (FlutsimConfig.enableAutoReload && !FlutsimConfig.enableInstantHotReload) {
          unifiedServer.triggerAutoReload();
        }

        // Trigger instant UI updates if enabled
        if (FlutsimConfig.enableInstantUIUpdates) {
          unifiedServer.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;
}