feat: Add tests for Wolf3dGuiApp and refactor game data picker management
- Introduced unit tests for the Wolf3dGuiApp to ensure proper directory configuration and audio management. - Refactored the GameDataPickerManager to streamline directory and file selection processes. - Removed obsolete GameDataPickerManager and Wolf3dAppManager classes to simplify the architecture. - Enhanced the Wolf3dFlutterEngine to improve game version handling during save loading. - Updated existing tests to reflect changes in the game data management structure. - Removed unnecessary dependencies and cleaned up the codebase for better maintainability. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -1,144 +0,0 @@
|
||||
library;
|
||||
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
|
||||
|
||||
/// Callback used to open a directory picker.
|
||||
typedef PickDirectoryCallback =
|
||||
Future<String?> Function({
|
||||
String? confirmButtonText,
|
||||
});
|
||||
|
||||
/// Callback used to open a multi-file picker.
|
||||
typedef PickFilesCallback = Future<List<XFile>> Function();
|
||||
|
||||
/// Coordinates game-data selection flows for the no-data setup screen.
|
||||
class GameDataPickerManager {
|
||||
/// Creates a picker workflow manager bound to [engine].
|
||||
///
|
||||
/// [pickDirectory] and [pickFiles] are injectable test seams. Production
|
||||
/// usage should rely on their defaults from `file_selector`.
|
||||
GameDataPickerManager({
|
||||
required this.engine,
|
||||
PickDirectoryCallback? pickDirectory,
|
||||
PickFilesCallback? pickFiles,
|
||||
}) : _pickDirectory = pickDirectory ?? getDirectoryPath,
|
||||
_pickFiles = pickFiles ?? openFiles;
|
||||
|
||||
/// Engine facade reloaded when users choose new data locations.
|
||||
final Wolf3dFlutterEngine engine;
|
||||
|
||||
final PickDirectoryCallback _pickDirectory;
|
||||
final PickFilesCallback _pickFiles;
|
||||
|
||||
bool _isLoadingGameData = false;
|
||||
String? _pickerError;
|
||||
|
||||
/// Whether a picker/reload operation is currently in progress.
|
||||
bool get isLoadingGameData => _isLoadingGameData;
|
||||
|
||||
/// Last picker/reload error, if any.
|
||||
String? get pickerError => _pickerError;
|
||||
|
||||
/// Prompts the user for a game-data directory and reloads discovery.
|
||||
///
|
||||
/// Calls [notifyChanged] before and after the async workflow so UIs can
|
||||
/// update loading/error state.
|
||||
Future<void> pickGameDataDirectory({
|
||||
required void Function() notifyChanged,
|
||||
}) async {
|
||||
_isLoadingGameData = true;
|
||||
_pickerError = null;
|
||||
notifyChanged();
|
||||
|
||||
try {
|
||||
final String? directoryPath = await _pickDirectory(
|
||||
confirmButtonText: 'Use this folder',
|
||||
);
|
||||
|
||||
if (directoryPath == null || directoryPath.trim().isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
await engine.init(directory: directoryPath.trim());
|
||||
} catch (error) {
|
||||
_pickerError = 'Unable to load selected directory: $error';
|
||||
} finally {
|
||||
_isLoadingGameData = false;
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// Prompts the user for one or more game-data files and reloads discovery.
|
||||
///
|
||||
/// Selected file paths are collapsed into unique parent directories, then
|
||||
/// passed to [Wolf3dFlutterEngine.init] as one primary directory plus
|
||||
/// [Wolf3dFlutterEngine.init] `additionalDirectories`.
|
||||
Future<void> pickGameDataFiles({
|
||||
required void Function() notifyChanged,
|
||||
}) async {
|
||||
_isLoadingGameData = true;
|
||||
_pickerError = null;
|
||||
notifyChanged();
|
||||
|
||||
try {
|
||||
final List<XFile> selectedFiles = await _pickFiles();
|
||||
|
||||
if (selectedFiles.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final LinkedHashSet<String> directories = LinkedHashSet<String>();
|
||||
for (final XFile file in selectedFiles) {
|
||||
final String directory = _directoryFromFilePath(file.path);
|
||||
if (directory.isNotEmpty) {
|
||||
directories.add(directory);
|
||||
}
|
||||
}
|
||||
|
||||
if (directories.isEmpty) {
|
||||
_pickerError = 'Selected files do not expose local filesystem paths.';
|
||||
return;
|
||||
}
|
||||
|
||||
final List<String> orderedDirectories = directories.toList(
|
||||
growable: false,
|
||||
);
|
||||
await engine.init(
|
||||
directory: orderedDirectories.first,
|
||||
additionalDirectories: orderedDirectories.skip(1),
|
||||
);
|
||||
} catch (error) {
|
||||
_pickerError = 'Unable to load selected files: $error';
|
||||
} finally {
|
||||
_isLoadingGameData = false;
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the parent directory component from [path].
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
|
||||
|
||||
/// Coordinates app-shell setup actions and shutdown behavior.
|
||||
class Wolf3dAppManager {
|
||||
/// Creates an app-shell manager bound to [engine].
|
||||
///
|
||||
/// [pickerManager] can be provided in tests to control picker behavior.
|
||||
Wolf3dAppManager({
|
||||
required this.engine,
|
||||
GameDataPickerManager? pickerManager,
|
||||
}) : _pickerManager = pickerManager ?? GameDataPickerManager(engine: engine);
|
||||
|
||||
/// Engine facade managed by the app shell.
|
||||
final Wolf3dFlutterEngine engine;
|
||||
final GameDataPickerManager _pickerManager;
|
||||
Future<void>? _shutdownFuture;
|
||||
|
||||
/// Forwarded picker loading flag for UI binding.
|
||||
bool get isLoadingGameData => _pickerManager.isLoadingGameData;
|
||||
|
||||
/// Forwarded picker error text for UI binding.
|
||||
String? get pickerError => _pickerManager.pickerError;
|
||||
|
||||
/// Shuts down engine audio exactly once and reuses in-flight shutdown calls.
|
||||
Future<void> ensureAudioShutdown() {
|
||||
final existing = _shutdownFuture;
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
final shutdown = engine.shutdownAudio();
|
||||
_shutdownFuture = shutdown;
|
||||
return shutdown;
|
||||
}
|
||||
|
||||
/// Delegates directory picker + reload behavior.
|
||||
Future<void> pickGameDataDirectory({
|
||||
required void Function() notifyChanged,
|
||||
}) async {
|
||||
await _pickerManager.pickGameDataDirectory(notifyChanged: notifyChanged);
|
||||
}
|
||||
|
||||
/// Delegates file picker + reload behavior.
|
||||
Future<void> pickGameDataFiles({
|
||||
required void Function() notifyChanged,
|
||||
}) async {
|
||||
await _pickerManager.pickGameDataFiles(notifyChanged: notifyChanged);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user