import 'dart:async'; import 'dart:io'; import 'package:flet/flet.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart' as path_provider; import 'package:serious_python/serious_python.dart'; import 'package:url_strategy/url_strategy.dart'; import 'package:window_manager/window_manager.dart'; import "python.dart"; {% for dep in cookiecutter.flutter.dependencies %} import 'package:{{ dep }}/{{ dep }}.dart' as {{ dep }}; {% endfor %} /* {% set show_boot_screen = get_pyproject("tool.flet." ~ cookiecutter.options.config_platform ~ ".app.boot_screen.show") or get_pyproject("tool.flet.app.boot_screen.show") or False %} {% set boot_screen_message = get_pyproject("tool.flet." ~ cookiecutter.options.config_platform ~ ".app.boot_screen.message") or get_pyproject("tool.flet.app.boot_screen.message") %} {% set show_startup_screen = get_pyproject("tool.flet." ~ cookiecutter.options.config_platform ~ ".app.startup_screen.show") or get_pyproject("tool.flet.app.startup_screen.show") or False %} {% set startup_screen_message = get_pyproject("tool.flet." ~ cookiecutter.options.config_platform ~ ".app.startup_screen.message") or get_pyproject("tool.flet.app.startup_screen.message") %} show_boot_screen: {{ show_boot_screen }} boot_screen_message: {{ boot_screen_message }} show_startup_screen: {{ show_startup_screen }} startup_screen_message: {{ startup_screen_message }} */ const bool isProduction = bool.fromEnvironment('dart.vm.product'); const assetPath = "app/app.zip"; const pythonModuleName = "{{ cookiecutter.python_module_name }}"; final showAppBootScreen = bool.tryParse("{{ show_boot_screen }}".toLowerCase()) ?? false; const appBootScreenMessage = '{{ boot_screen_message | default("Preparing the app for its first launch…", true) }}'; final showAppStartupScreen = bool.tryParse("{{ show_startup_screen }}".toLowerCase()) ?? false; const appStartupScreenMessage = '{{ startup_screen_message | default("Getting things ready…", true) }}'; List extensions = [ {% for dep in cookiecutter.flutter.dependencies %} {{ dep }}.Extension(), {% endfor %} ]; String outLogFilename = ""; // global vars List _args = []; String pageUrl = ""; String assetsDir = ""; String appDir = ""; Map environmentVariables = {}; void main(List args) async { _args = List.from(args); for (var ext in extensions) { ext.ensureInitialized(); } runApp(FutureBuilder( future: prepareApp(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { // OK - start Python program return kIsWeb || (isDesktopPlatform() && _args.isNotEmpty) ? FletApp( pageUrl: pageUrl, assetsDir: assetsDir, showAppStartupScreen: showAppStartupScreen, appStartupScreenMessage: appStartupScreenMessage, extensions: extensions) : FutureBuilder( future: runPythonApp(args), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData || snapshot.hasError) { // error or premature finish return MaterialApp( home: ErrorScreen( title: "Error running app", text: snapshot.data ?? snapshot.error.toString()), ); } else { // no result of error return FletApp( pageUrl: pageUrl, assetsDir: assetsDir, showAppStartupScreen: showAppStartupScreen, appStartupScreenMessage: appStartupScreenMessage, extensions: extensions); } }); } else if (snapshot.hasError) { // error return MaterialApp( home: ErrorScreen( title: "Error starting app", text: snapshot.error.toString())); } else { // loading return MaterialApp(home: showAppBootScreen ? const BootScreen() : const BlankScreen()); } })); } Future prepareApp() async { if (!_args.contains("--debug")) { // ignore: avoid_returning_null_for_void debugPrint = (String? message, {int? wrapWidth}) => null; } else { _args.remove("--debug"); } await setupDesktop(); var devPageUrl = const String.fromEnvironment("FLET_PAGE_URL"); if (kIsWeb) { // web mode - connect via HTTP pageUrl = Uri.base.toString(); var routeUrlStrategy = getFletRouteUrlStrategy(); if (routeUrlStrategy == "path") { setPathUrlStrategy(); } } else if ((_args.isNotEmpty || devPageUrl != "") && isDesktopPlatform()) { // developer mode debugPrint("Flet app is running in Developer mode"); pageUrl = devPageUrl; if (_args.isNotEmpty && pageUrl == "") { pageUrl = _args[0]; } if (_args.length > 1) { var pidFilePath = _args[1]; debugPrint("Args contain a path to PID file: $pidFilePath}"); var pidFile = await File(pidFilePath).create(); await pidFile.writeAsString("$pid"); } if (_args.length > 2) { assetsDir = _args[2]; debugPrint("Args contain a path assets directory: $assetsDir}"); } } else { // production mode // extract app from asset appDir = await extractAssetZip(assetPath, checkHash: true); // set current directory to app path Directory.current = appDir; assetsDir = path.join(appDir, "assets"); // configure apps DATA and TEMP directories WidgetsFlutterBinding.ensureInitialized(); var appTempPath = (await path_provider.getApplicationCacheDirectory()).path; var appDataPath = (await path_provider.getApplicationDocumentsDirectory()).path; if (defaultTargetPlatform != TargetPlatform.iOS && defaultTargetPlatform != TargetPlatform.android) { // append app name to the path and create dir PackageInfo packageInfo = await PackageInfo.fromPlatform(); appDataPath = path.join(appDataPath, "flet", packageInfo.packageName); if (!await Directory(appDataPath).exists()) { await Directory(appDataPath).create(recursive: true); } } environmentVariables["FLET_APP_STORAGE_DATA"] = appDataPath; environmentVariables["FLET_APP_STORAGE_TEMP"] = appTempPath; outLogFilename = path.join(appTempPath, "console.log"); environmentVariables["FLET_APP_CONSOLE"] = outLogFilename; environmentVariables["FLET_PLATFORM"] = defaultTargetPlatform.name.toLowerCase(); if (defaultTargetPlatform == TargetPlatform.windows) { // use TCP on Windows var tcpPort = await getUnusedPort(); pageUrl = "tcp://localhost:$tcpPort"; environmentVariables["FLET_SERVER_PORT"] = tcpPort.toString(); } else { // use UDS on other platforms pageUrl = "flet_$pid.sock"; environmentVariables["FLET_SERVER_UDS_PATH"] = pageUrl; } } return ""; } Future runPythonApp(List args) async { var argvItems = args.map((a) => "\"${a.replaceAll('"', '\\"')}\""); var argv = "[${argvItems.isNotEmpty ? argvItems.join(',') : '""'}]"; var script = pythonScript .replaceAll("{outLogFilename}", outLogFilename.replaceAll("\\", "\\\\")) .replaceAll('{module_name}', pythonModuleName) .replaceAll('{argv}', argv); var completer = Completer(); ServerSocket outSocketServer; String socketAddr = ""; StringBuffer pythonOut = StringBuffer(); if (defaultTargetPlatform == TargetPlatform.windows) { var tcpAddr = "127.0.0.1"; outSocketServer = await ServerSocket.bind(tcpAddr, 0); debugPrint( 'Python output TCP Server is listening on port ${outSocketServer.port}'); socketAddr = "$tcpAddr:${outSocketServer.port}"; } else { socketAddr = "stdout_$pid.sock"; if (await File(socketAddr).exists()) { await File(socketAddr).delete(); } outSocketServer = await ServerSocket.bind( InternetAddress(socketAddr, type: InternetAddressType.unix), 0); debugPrint('Python output Socket Server is listening on $socketAddr'); } environmentVariables["FLET_PYTHON_CALLBACK_SOCKET_ADDR"] = socketAddr; void closeOutServer() async { outSocketServer.close(); int exitCode = int.tryParse(pythonOut.toString().trim()) ?? 0; if (exitCode == errorExitCode) { var out = ""; if (await File(outLogFilename).exists()) { out = await File(outLogFilename).readAsString(); } completer.complete(out); } else { exit(exitCode); } } outSocketServer.listen((client) { debugPrint( 'Connection from: ${client.remoteAddress.address}:${client.remotePort}'); client.listen((data) { var s = String.fromCharCodes(data); pythonOut.write(s); }, onError: (error) { client.close(); closeOutServer(); }, onDone: () { client.close(); closeOutServer(); }); }); // run python async SeriousPython.runProgram(path.join(appDir, "$pythonModuleName.pyc"), script: script, environmentVariables: environmentVariables); // wait for client connection to close return completer.future; } class ErrorScreen extends StatelessWidget { final String title; final String text; const ErrorScreen({super.key, required this.title, required this.text}); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Container( padding: const EdgeInsets.all(8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: Theme.of(context).textTheme.titleMedium, ), TextButton.icon( onPressed: () { Clipboard.setData(ClipboardData(text: text)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Copied to clipboard')), ); }, icon: const Icon( Icons.copy, size: 16, ), label: const Text("Copy"), ) ], ), Expanded( child: SingleChildScrollView( child: SelectableText(text, style: Theme.of(context).textTheme.bodySmall), )) ], ), )), ); } } class BootScreen extends StatelessWidget { const BootScreen({ super.key, }); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox( width: 30, height: 30, child: CircularProgressIndicator(strokeWidth: 3), ), const SizedBox( height: 10, ), Text(appBootScreenMessage, style: Theme.of(context).textTheme.bodySmall,) ], ), ), ); } } class BlankScreen extends StatelessWidget { const BlankScreen({ super.key, }); @override Widget build(BuildContext context) { return const Scaffold( body: SizedBox.shrink(), ); } } Future getUnusedPort() { return ServerSocket.bind("127.0.0.1", 0).then((socket) { var port = socket.port; socket.close(); return port; }); } Future setupDesktop() async { if (isDesktopPlatform()) { WidgetsFlutterBinding.ensureInitialized(); await windowManager.ensureInitialized(); Map env = Platform.environment; var hideWindowOnStart = env["FLET_HIDE_WINDOW_ON_START"]; var hideAppOnStart = env["FLET_HIDE_APP_ON_START"]; debugPrint("hideWindowOnStart: $hideWindowOnStart"); debugPrint("hideAppOnStart: $hideAppOnStart"); await windowManager.waitUntilReadyToShow(null, () async { if (hideWindowOnStart == null && hideAppOnStart == null) { await windowManager.show(); await windowManager.focus(); } else if (hideAppOnStart != null) { await windowManager.setSkipTaskbar(true); } }); } }