feat: Add GameDataPickerManager for managing game data selection flows

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-23 19:38:34 +01:00
parent 6441592534
commit 1394c20134
3 changed files with 118 additions and 89 deletions
@@ -0,0 +1,109 @@
library;
import 'dart:collection';
import 'package:file_selector/file_selector.dart';
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
/// Coordinates game-data selection flows for the no-data setup screen.
class GameDataPickerManager {
GameDataPickerManager({required this.engine});
final Wolf3dFlutterEngine engine;
bool _isLoadingGameData = false;
String? _pickerError;
bool get isLoadingGameData => _isLoadingGameData;
String? get pickerError => _pickerError;
Future<void> pickGameDataDirectory({
required void Function() notifyChanged,
}) async {
_isLoadingGameData = true;
_pickerError = null;
notifyChanged();
try {
final String? directoryPath = await getDirectoryPath(
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();
}
}
Future<void> pickGameDataFiles({
required void Function() notifyChanged,
}) async {
_isLoadingGameData = true;
_pickerError = null;
notifyChanged();
try {
final List<XFile> selectedFiles = await openFiles();
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();
}
}
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,9 +1,7 @@
library; library;
import 'dart:async'; import 'dart:async';
import 'dart:collection';
import 'package:file_selector/file_selector.dart';
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart'; import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
/// Coordinates app-shell setup actions and shutdown behavior. /// Coordinates app-shell setup actions and shutdown behavior.
@@ -11,13 +9,13 @@ class Wolf3dAppManager {
Wolf3dAppManager({required this.engine}); Wolf3dAppManager({required this.engine});
final Wolf3dFlutterEngine engine; final Wolf3dFlutterEngine engine;
late final GameDataPickerManager _pickerManager = GameDataPickerManager(
bool _isLoadingGameData = false; engine: engine,
String? _pickerError; );
Future<void>? _shutdownFuture; Future<void>? _shutdownFuture;
bool get isLoadingGameData => _isLoadingGameData; bool get isLoadingGameData => _pickerManager.isLoadingGameData;
String? get pickerError => _pickerError; String? get pickerError => _pickerManager.pickerError;
Future<void> ensureAudioShutdown() { Future<void> ensureAudioShutdown() {
final existing = _shutdownFuture; final existing = _shutdownFuture;
@@ -33,91 +31,12 @@ class Wolf3dAppManager {
Future<void> pickGameDataDirectory({ Future<void> pickGameDataDirectory({
required void Function() notifyChanged, required void Function() notifyChanged,
}) async { }) async {
_isLoadingGameData = true; await _pickerManager.pickGameDataDirectory(notifyChanged: notifyChanged);
_pickerError = null;
notifyChanged();
try {
final String? directoryPath = await getDirectoryPath(
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();
}
} }
Future<void> pickGameDataFiles({ Future<void> pickGameDataFiles({
required void Function() notifyChanged, required void Function() notifyChanged,
}) async { }) async {
_isLoadingGameData = true; await _pickerManager.pickGameDataFiles(notifyChanged: notifyChanged);
_pickerError = null;
notifyChanged();
try {
final List<XFile> selectedFiles = await openFiles();
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();
}
}
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);
} }
} }
@@ -18,6 +18,7 @@ export 'audio/wolf3d_platform_audio.dart' show Wolf3dPlatformAudio;
export 'managers/game_app_lifecycle_manager.dart' show GameAppLifecycleManager; export 'managers/game_app_lifecycle_manager.dart' show GameAppLifecycleManager;
export 'managers/game_data_directory_persistence.dart' export 'managers/game_data_directory_persistence.dart'
show DefaultGameDataDirectoryPersistence; show DefaultGameDataDirectoryPersistence;
export 'managers/game_data_picker_manager.dart' show GameDataPickerManager;
export 'managers/game_display_manager.dart' show GameDisplayManager; export 'managers/game_display_manager.dart' show GameDisplayManager;
export 'managers/game_persistence_manager.dart' show GamePersistenceManager; export 'managers/game_persistence_manager.dart' show GamePersistenceManager;
export 'managers/game_renderer_mode_manager.dart' export 'managers/game_renderer_mode_manager.dart'
@@ -30,7 +31,7 @@ export 'managers/game_screen_input_manager.dart'
HostShortcutRegistry, HostShortcutRegistry,
GameScreenInputManager, GameScreenInputManager,
isAltEnterShortcut; isAltEnterShortcut;
export 'managers/wolf3d_app_manager.dart' show Wolf3dAppManager; export 'managers/wolf3d_app_manager.dart' show Wolf3dAppManager;
export 'screens/audio_gallery.dart' show AudioGallery; export 'screens/audio_gallery.dart' show AudioGallery;
export 'screens/debug_tools_screen.dart' show DebugToolsScreen; export 'screens/debug_tools_screen.dart' show DebugToolsScreen;
export 'screens/game_screen.dart' show GameScreen; export 'screens/game_screen.dart' show GameScreen;