feat: Introduce Wolf3dAppManager for managing audio shutdown and game data directory selection
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:file_selector/file_selector.dart';
|
||||
import 'package:wolf_3d_flutter/wolf_3d_flutter.dart';
|
||||
|
||||
/// Coordinates app-shell setup actions and shutdown behavior.
|
||||
class Wolf3dAppManager {
|
||||
Wolf3dAppManager({required this.engine});
|
||||
|
||||
final Wolf3dFlutterEngine engine;
|
||||
|
||||
bool _isLoadingGameData = false;
|
||||
String? _pickerError;
|
||||
Future<void>? _shutdownFuture;
|
||||
|
||||
bool get isLoadingGameData => _isLoadingGameData;
|
||||
String? get pickerError => _pickerError;
|
||||
|
||||
Future<void> ensureAudioShutdown() {
|
||||
final existing = _shutdownFuture;
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
final shutdown = engine.shutdownAudio();
|
||||
_shutdownFuture = shutdown;
|
||||
return shutdown;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -21,37 +21,47 @@ class Wolf3dApp extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _Wolf3dAppState extends State<Wolf3dApp> with WidgetsBindingObserver {
|
||||
Future<void>? _shutdownFuture;
|
||||
late final Wolf3dAppManager _manager;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_manager = Wolf3dAppManager(engine: widget.engine);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
unawaited(_ensureAudioShutdown());
|
||||
unawaited(_manager.ensureAudioShutdown());
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.detached) {
|
||||
unawaited(_ensureAudioShutdown());
|
||||
unawaited(_manager.ensureAudioShutdown());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _ensureAudioShutdown() {
|
||||
final Future<void>? existing = _shutdownFuture;
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
Future<void> _pickGameDataDirectory() {
|
||||
return _manager.pickGameDataDirectory(
|
||||
notifyChanged: () {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final Future<void> shutdown = widget.engine.shutdownAudio();
|
||||
_shutdownFuture = shutdown;
|
||||
return shutdown;
|
||||
Future<void> _pickGameDataFiles() {
|
||||
return _manager.pickGameDataFiles(
|
||||
notifyChanged: () {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -59,6 +69,10 @@ class _Wolf3dAppState extends State<Wolf3dApp> with WidgetsBindingObserver {
|
||||
return widget.engine.availableGames.isEmpty
|
||||
? NoGameDataScreen(
|
||||
configuredDataDirectory: widget.engine.configuredDataDirectory,
|
||||
onPickGameDataDirectory: _pickGameDataDirectory,
|
||||
onPickGameDataFiles: _pickGameDataFiles,
|
||||
isLoadingGameData: _manager.isLoadingGameData,
|
||||
pickerError: _manager.pickerError,
|
||||
)
|
||||
: GameScreen(wolf3d: widget.engine);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export 'managers/game_screen_input_manager.dart'
|
||||
HostShortcutRegistry,
|
||||
GameScreenInputManager,
|
||||
isAltEnterShortcut;
|
||||
export 'managers/wolf3d_app_manager.dart' show Wolf3dAppManager;
|
||||
export 'screens/audio_gallery.dart' show AudioGallery;
|
||||
export 'screens/debug_tools_screen.dart' show DebugToolsScreen;
|
||||
export 'screens/game_screen.dart' show GameScreen;
|
||||
|
||||
Reference in New Issue
Block a user