530 lines
17 KiB
Dart
530 lines
17 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:nes_ui/nes_ui.dart';
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:shitman/game/shitman_game.dart';
|
|
import 'package:shitman/settings/app_settings.dart';
|
|
|
|
class InGameUI extends StatelessWidget with AppSettings {
|
|
static const String overlayID = 'InGameUI';
|
|
final ShitmanGame game;
|
|
|
|
InGameUI(this.game, {super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: Stack(
|
|
children: [
|
|
// Top HUD
|
|
Positioned(
|
|
top: 20,
|
|
left: 20,
|
|
right: 20,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
// Stealth indicator
|
|
NesContainer(
|
|
backgroundColor: Colors.black87,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.visibility_off, size: 16),
|
|
SizedBox(width: 8),
|
|
Text(
|
|
'gameplay.hidden'.tr(),
|
|
style: TextStyle(color: Colors.green),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
// Mission objective
|
|
NesContainer(
|
|
backgroundColor: Colors.black87,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text(
|
|
'gameplay.find_target'.tr(),
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
),
|
|
),
|
|
// Pause button
|
|
IconButton(
|
|
icon: Icon(Icons.pause, color: Colors.white),
|
|
onPressed: () => game.overlays.add(PauseMenuUI.overlayID),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Bottom controls hint
|
|
Positioned(
|
|
bottom: 20,
|
|
left: 20,
|
|
right: 20,
|
|
child: NesContainer(
|
|
backgroundColor: Colors.black54,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'controls.move'.tr(),
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
Text(
|
|
'controls.place_bag'.tr(),
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
Text(
|
|
'controls.ring_bell'.tr(),
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MainMenuUI extends StatelessWidget with AppSettings {
|
|
static const String overlayID = 'MainMenu';
|
|
final ShitmanGame game;
|
|
|
|
MainMenuUI(this.game, {super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Material(
|
|
color: Colors.black,
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
// Game title
|
|
Text(
|
|
'game.title'.tr(),
|
|
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
|
color: Colors.orange,
|
|
fontSize: 48,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
'game.subtitle'.tr(),
|
|
style: TextStyle(color: Colors.white70, fontSize: 16),
|
|
),
|
|
SizedBox(height: 40),
|
|
|
|
// Menu buttons
|
|
Column(
|
|
children: [
|
|
NesButton(
|
|
type: NesButtonType.primary,
|
|
onPressed: () async {
|
|
game.overlays.remove(MainMenuUI.overlayID);
|
|
game.overlays.add(InGameUI.overlayID);
|
|
await game.startGame();
|
|
},
|
|
child: Text('menu.start_mission'.tr()),
|
|
),
|
|
SizedBox(height: 16),
|
|
NesButton(
|
|
type: NesButtonType.normal,
|
|
onPressed: () {
|
|
game.overlays.remove(MainMenuUI.overlayID);
|
|
game.overlays.add(SettingsUI.overlayID);
|
|
},
|
|
child: Text('menu.settings'.tr()),
|
|
),
|
|
SizedBox(height: 16),
|
|
NesButton(
|
|
type: NesButtonType.normal,
|
|
onPressed: () async => await game.startInfiniteMode(),
|
|
child: Text('menu.infinite_mode'.tr()),
|
|
),
|
|
],
|
|
),
|
|
|
|
SizedBox(height: 40),
|
|
Text(
|
|
'game.description'.tr(),
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(color: Colors.white54, fontSize: 12),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SettingsUI extends StatefulWidget with AppSettings {
|
|
static const String overlayID = 'Settings';
|
|
final ShitmanGame game;
|
|
|
|
SettingsUI(this.game, {super.key});
|
|
|
|
@override
|
|
State<SettingsUI> createState() => _SettingsUIState();
|
|
}
|
|
|
|
class _SettingsUIState extends State<SettingsUI> with AppSettings {
|
|
bool showVisionCones = false;
|
|
bool showDetectionRadius = false;
|
|
bool debugMode = false;
|
|
bool touchControlsEnabled = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadSettings();
|
|
}
|
|
|
|
void _loadSettings() {
|
|
try {
|
|
showVisionCones = widget.game.appSettings.getBool(
|
|
'game.show_vision_cones',
|
|
);
|
|
showDetectionRadius = widget.game.appSettings.getBool(
|
|
'game.show_detection_radius',
|
|
);
|
|
debugMode = widget.game.appSettings.getBool('game.debug_mode');
|
|
touchControlsEnabled = widget.game.appSettings.getBool(
|
|
'ui.touch_enabled',
|
|
);
|
|
} catch (e) {
|
|
// Settings not ready, use defaults
|
|
showVisionCones = false;
|
|
showDetectionRadius = false;
|
|
debugMode = false;
|
|
touchControlsEnabled = false;
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Material(
|
|
color: Colors.black87,
|
|
child: Center(
|
|
child: NesContainer(
|
|
backgroundColor: Colors.black,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'menu.settings'.tr(),
|
|
style: Theme.of(
|
|
context,
|
|
).textTheme.headlineMedium?.copyWith(color: Colors.white),
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.close, color: Colors.white),
|
|
onPressed: () {
|
|
widget.game.overlays.remove(SettingsUI.overlayID);
|
|
widget.game.overlays.add(MainMenuUI.overlayID);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 24),
|
|
|
|
// Language selector
|
|
Row(
|
|
children: [
|
|
Text(
|
|
'settings.language_title'.tr(),
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
SizedBox(width: 8),
|
|
Icon(Icons.language, color: Colors.white),
|
|
],
|
|
),
|
|
SizedBox(height: 8),
|
|
NesDropdownMenu<String>(
|
|
initialValue: context.locale.languageCode,
|
|
entries: [
|
|
NesDropdownMenuEntry(
|
|
value: 'en',
|
|
label: 'settings.language.english'.tr(),
|
|
),
|
|
NesDropdownMenuEntry(
|
|
value: 'da',
|
|
label: 'settings.language.danish'.tr(),
|
|
),
|
|
NesDropdownMenuEntry(
|
|
value: 'de',
|
|
label: 'settings.language.german'.tr(),
|
|
),
|
|
],
|
|
onChanged: (String? value) async {
|
|
if (value != null) {
|
|
context.setLocale(Locale(value));
|
|
// Save language preference
|
|
await widget.game.appSettings.setString('ui.language', value);
|
|
setState(() {}); // Simple setState to rebuild UI
|
|
}
|
|
},
|
|
),
|
|
|
|
SizedBox(height: 16),
|
|
|
|
// Gameplay Settings Section
|
|
Text(
|
|
'settings.gameplay'.tr(),
|
|
style: TextStyle(
|
|
color: Colors.orange,
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
SizedBox(height: 12),
|
|
|
|
// Vision Cones toggle
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'settings.show_vision_cones'.tr(),
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
Text(
|
|
'settings.vision_cones_help'.tr(),
|
|
style: TextStyle(
|
|
color: Colors.white60,
|
|
fontSize: 11,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
NesCheckBox(
|
|
value: showVisionCones,
|
|
onChange: (value) async {
|
|
setState(() {
|
|
showVisionCones = value;
|
|
});
|
|
await widget.game.appSettings.setBool(
|
|
'game.show_vision_cones',
|
|
value,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 8),
|
|
|
|
// Detection Radius toggle
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'settings.show_detection_radius'.tr(),
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
Text(
|
|
'settings.detection_help'.tr(),
|
|
style: TextStyle(
|
|
color: Colors.white60,
|
|
fontSize: 11,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
NesCheckBox(
|
|
value: showDetectionRadius,
|
|
onChange: (value) async {
|
|
setState(() {
|
|
showDetectionRadius = value;
|
|
});
|
|
await widget.game.appSettings.setBool(
|
|
'game.show_detection_radius',
|
|
value,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
// Accessibility Section
|
|
Text(
|
|
'settings.accessibility'.tr(),
|
|
style: TextStyle(
|
|
color: Colors.orange,
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
SizedBox(height: 12),
|
|
|
|
// Debug mode toggle
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'ui.debug_mode'.tr(),
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
NesCheckBox(
|
|
value: debugMode,
|
|
onChange: (value) async {
|
|
setState(() {
|
|
debugMode = value;
|
|
});
|
|
await widget.game.appSettings.setBool(
|
|
'game.debug_mode',
|
|
value,
|
|
);
|
|
widget.game.debugMode = value;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
|
|
SizedBox(height: 8),
|
|
|
|
// Touch controls toggle
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'settings.touch_controls'.tr(),
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
Text(
|
|
'settings.touch_controls_help'.tr(),
|
|
style: TextStyle(
|
|
color: Colors.white60,
|
|
fontSize: 11,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
NesCheckBox(
|
|
value: touchControlsEnabled,
|
|
onChange: (value) async {
|
|
setState(() {
|
|
touchControlsEnabled = value;
|
|
});
|
|
await widget.game.appSettings.setBool(
|
|
'ui.touch_enabled',
|
|
value,
|
|
);
|
|
widget.game.toggleTouchControls(value);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
|
|
SizedBox(height: 40),
|
|
Center(
|
|
child: NesButton(
|
|
type: NesButtonType.primary,
|
|
onPressed: () {
|
|
widget.game.overlays.remove(SettingsUI.overlayID);
|
|
widget.game.overlays.add(MainMenuUI.overlayID);
|
|
},
|
|
child: Text('menu.back_to_menu'.tr()),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class PauseMenuUI extends StatelessWidget {
|
|
static const String overlayID = 'PauseMenu';
|
|
final ShitmanGame game;
|
|
|
|
const PauseMenuUI(this.game, {super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Material(
|
|
color: Colors.black54,
|
|
child: Center(
|
|
child: NesContainer(
|
|
backgroundColor: Colors.black,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'ui.paused'.tr(),
|
|
style: Theme.of(
|
|
context,
|
|
).textTheme.headlineMedium?.copyWith(color: Colors.white),
|
|
),
|
|
SizedBox(height: 24),
|
|
|
|
NesButton(
|
|
type: NesButtonType.primary,
|
|
onPressed: () => game.overlays.remove(PauseMenuUI.overlayID),
|
|
child: Text('menu.resume'.tr()),
|
|
),
|
|
SizedBox(height: 12),
|
|
NesButton(
|
|
type: NesButtonType.normal,
|
|
onPressed: () {
|
|
game.overlays.remove(PauseMenuUI.overlayID);
|
|
game.overlays.remove(InGameUI.overlayID);
|
|
game.overlays.add(SettingsUI.overlayID);
|
|
},
|
|
child: Text('menu.settings'.tr()),
|
|
),
|
|
SizedBox(height: 12),
|
|
NesButton(
|
|
type: NesButtonType.warning,
|
|
onPressed: () {
|
|
game.overlays.remove(PauseMenuUI.overlayID);
|
|
game.overlays.remove(InGameUI.overlayID);
|
|
game.overlays.add(MainMenuUI.overlayID);
|
|
game.stopGame();
|
|
},
|
|
child: Text('menu.main_menu'.tr()),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|