library; import 'dart:collection'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:wolf_3d_flutter/wolf_3d_flutter.dart'; /// Minimal app shell that binds a prepared [Wolf3dFlutterEngine] instance to /// host screens. class Wolf3dApp extends StatefulWidget { /// Shared initialized facade that owns game data, input, and audio services. final Wolf3dFlutterEngine engine; const Wolf3dApp({ super.key, required this.engine, }); @override State createState() => _Wolf3dAppState(); } class _Wolf3dAppState extends State { bool _isLoadingGameData = false; String? _pickerError; Future _pickGameDataDirectory() async { setState(() { _isLoadingGameData = true; _pickerError = null; }); try { final String? directoryPath = await getDirectoryPath( confirmButtonText: 'Use this folder', ); if (directoryPath == null || directoryPath.trim().isEmpty) { return; } await widget.engine.init(directory: directoryPath.trim()); } catch (error) { setState(() { _pickerError = 'Unable to load selected directory: $error'; }); } finally { if (mounted) { setState(() { _isLoadingGameData = false; }); } } } Future _pickGameDataFiles() async { setState(() { _isLoadingGameData = true; _pickerError = null; }); try { final List selectedFiles = await openFiles(); if (selectedFiles.isEmpty) { return; } final LinkedHashSet directories = LinkedHashSet(); for (final XFile file in selectedFiles) { final String directory = _directoryFromFilePath(file.path); if (directory.isNotEmpty) { directories.add(directory); } } if (directories.isEmpty) { setState(() { _pickerError = 'Selected files do not expose local filesystem paths.'; }); return; } final List orderedDirectories = directories.toList( growable: false, ); await widget.engine.init( directory: orderedDirectories.first, additionalDirectories: orderedDirectories.skip(1), ); } catch (error) { setState(() { _pickerError = 'Unable to load selected files: $error'; }); } finally { if (mounted) { setState(() { _isLoadingGameData = false; }); } } } String _directoryFromFilePath(String path) { final String trimmedPath = path.trim(); if (trimmedPath.isEmpty) { return ''; } final int slashIndex = trimmedPath.lastIndexOf('/'); final int backslashIndex = trimmedPath.lastIndexOf(r'\'); final int separatorIndex = slashIndex > backslashIndex ? slashIndex : backslashIndex; if (separatorIndex < 0) { return ''; } if (separatorIndex == 0) { return trimmedPath[0]; } return trimmedPath.substring(0, separatorIndex); } @override Widget build(BuildContext context) { return widget.engine.availableGames.isEmpty ? NoGameDataScreen( configuredDataDirectory: widget.engine.configuredDataDirectory, onPickGameDataDirectory: _pickGameDataDirectory, onPickGameDataFiles: _pickGameDataFiles, isLoadingGameData: _isLoadingGameData, pickerError: _pickerError, ) : GameScreen(wolf3d: widget.engine); } }