diff --git a/lib/features/tournament/presentation/provider/player_provider.dart b/lib/features/tournament/presentation/provider/player_provider.dart index e8eb8fa..2344fb2 100644 --- a/lib/features/tournament/presentation/provider/player_provider.dart +++ b/lib/features/tournament/presentation/provider/player_provider.dart @@ -68,4 +68,12 @@ class PlayerNotifier extends StateNotifier> { } state = box.values.toList(); } + + void resetAllWins(){ + for(final player in box.values){ + player.wins = 0; + player.save(); + } + state = box.values.toList(); + } } \ No newline at end of file diff --git a/lib/features/tournament/presentation/provider/shuffle_settings_provider.dart b/lib/features/tournament/presentation/provider/shuffle_settings_provider.dart new file mode 100644 index 0000000..b05e9df --- /dev/null +++ b/lib/features/tournament/presentation/provider/shuffle_settings_provider.dart @@ -0,0 +1,99 @@ +// lib/features/tournament/presentation/provider/shuffle_settings_provider.dart + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:hive/hive.dart'; + +part 'shuffle_settings_provider.g.dart'; // Für den Hive-Generator + +// Enum mit Hive-Annotationen +@HiveType(typeId: 5) // Freie typeId wählen (z. B. 5, nicht bereits vergeben) +enum ShuffleMode { + @HiveField(0) + random, + + @HiveField(1) + balanced, + + @HiveField(2) + aggressive, + + @HiveField(3) + positionBased, +} + +// ShuffleSettings-Klasse +@HiveType(typeId: 4) +class ShuffleSettings extends HiveObject { + @HiveField(0) + ShuffleMode mode; + + @HiveField(1) + bool prioritizeGenderBalance; + + @HiveField(2) + bool prioritizeLevelBalance; + + @HiveField(3) + bool usePositions; + + ShuffleSettings({ + this.mode = ShuffleMode.balanced, + this.prioritizeGenderBalance = true, + this.prioritizeLevelBalance = true, + this.usePositions = true, + }); + + // copyWith für immutable Updates + ShuffleSettings copyWith({ + ShuffleMode? mode, + bool? prioritizeGenderBalance, + bool? prioritizeLevelBalance, + bool? usePositions, + }) { + return ShuffleSettings( + mode: mode ?? this.mode, + prioritizeGenderBalance: prioritizeGenderBalance ?? this.prioritizeGenderBalance, + prioritizeLevelBalance: prioritizeLevelBalance ?? this.prioritizeLevelBalance, + usePositions: usePositions ?? this.usePositions, + ); + } +} + +// Provider +final shuffleSettingsProvider = StateNotifierProvider((ref) { + return ShuffleSettingsNotifier(); +}); + +class ShuffleSettingsNotifier extends StateNotifier { + static const String _boxName = 'shuffle_settings'; + static const String _key = 'settings'; + + ShuffleSettingsNotifier() : super(ShuffleSettings()) { + _load(); + } + + Future _load() async { + final box = await Hive.openBox(_boxName); + final saved = box.get(_key); + if (saved != null) { + state = saved; + } + } + + Future update({ + ShuffleMode? mode, + bool? prioritizeGenderBalance, + bool? prioritizeLevelBalance, + bool? usePositions, + }) async { + state = state.copyWith( + mode: mode, + prioritizeGenderBalance: prioritizeGenderBalance, + prioritizeLevelBalance: prioritizeLevelBalance, + usePositions: usePositions, + ); + + final box = await Hive.openBox(_boxName); + await box.put(_key, state); + } +} \ No newline at end of file diff --git a/lib/features/tournament/presentation/provider/shuffle_settings_provider.g.dart b/lib/features/tournament/presentation/provider/shuffle_settings_provider.g.dart new file mode 100644 index 0000000..fb927ed --- /dev/null +++ b/lib/features/tournament/presentation/provider/shuffle_settings_provider.g.dart @@ -0,0 +1,99 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'shuffle_settings_provider.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class ShuffleSettingsAdapter extends TypeAdapter { + @override + final int typeId = 4; + + @override + ShuffleSettings read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return ShuffleSettings( + mode: fields[0] as ShuffleMode, + prioritizeGenderBalance: fields[1] as bool, + prioritizeLevelBalance: fields[2] as bool, + usePositions: fields[3] as bool, + ); + } + + @override + void write(BinaryWriter writer, ShuffleSettings obj) { + writer + ..writeByte(4) + ..writeByte(0) + ..write(obj.mode) + ..writeByte(1) + ..write(obj.prioritizeGenderBalance) + ..writeByte(2) + ..write(obj.prioritizeLevelBalance) + ..writeByte(3) + ..write(obj.usePositions); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ShuffleSettingsAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} + +class ShuffleModeAdapter extends TypeAdapter { + @override + final int typeId = 5; + + @override + ShuffleMode read(BinaryReader reader) { + switch (reader.readByte()) { + case 0: + return ShuffleMode.random; + case 1: + return ShuffleMode.balanced; + case 2: + return ShuffleMode.aggressive; + case 3: + return ShuffleMode.positionBased; + default: + return ShuffleMode.random; + } + } + + @override + void write(BinaryWriter writer, ShuffleMode obj) { + switch (obj) { + case ShuffleMode.random: + writer.writeByte(0); + break; + case ShuffleMode.balanced: + writer.writeByte(1); + break; + case ShuffleMode.aggressive: + writer.writeByte(2); + break; + case ShuffleMode.positionBased: + writer.writeByte(3); + break; + } + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ShuffleModeAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/features/tournament/presentation/screens/fields_screen.dart b/lib/features/tournament/presentation/screens/fields_screen.dart index 6d1fcbf..6d7b225 100644 --- a/lib/features/tournament/presentation/screens/fields_screen.dart +++ b/lib/features/tournament/presentation/screens/fields_screen.dart @@ -4,6 +4,7 @@ import '../../data/models/player_model.dart'; import '../provider/active_players_provider.dart'; import '../provider/player_provider.dart'; // für awardWinToPlayers import '../widgets/volleyball_field_widget.dart'; +import 'package:flutter/services.dart'; class FieldsScreen extends ConsumerWidget { const FieldsScreen({super.key}); @@ -66,13 +67,14 @@ class FieldsScreen extends ConsumerWidget { ), onPressed: teamA.isEmpty ? null - : () { + : () async { + await HapticFeedback.mediumImpact(); ref.read(playerListProvider.notifier).awardWinToPlayers(teamA); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Oben gewinnt! +1 Schleifchen für ${teamA.length} Spieler'), backgroundColor: Colors.green, - duration: const Duration(seconds: 2), + duration: const Duration(seconds: 1), ), ); }, @@ -93,13 +95,14 @@ class FieldsScreen extends ConsumerWidget { ), onPressed: teamB.isEmpty ? null - : () { + : () async{ + HapticFeedback.mediumImpact(); ref.read(playerListProvider.notifier).awardWinToPlayers(teamB); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Unten gewinnt! +1 Schleifchen für ${teamB.length} Spieler'), backgroundColor: Colors.red, - duration: const Duration(seconds: 2), + duration: const Duration(seconds: 1), ), ); }, @@ -144,7 +147,8 @@ class FieldsScreen extends ConsumerWidget { child: ElevatedButton.icon( icon: const Icon(Icons.shuffle), label: const Text('Neu mischen', style: TextStyle(fontSize: 18)), - onPressed: () { + onPressed: () async{ + HapticFeedback.mediumImpact(); ref.invalidate(activePlayersProvider); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Neue Teams gemischt!'), duration: Duration(milliseconds: 100)), diff --git a/lib/features/tournament/presentation/screens/home_screen.dart b/lib/features/tournament/presentation/screens/home_screen.dart index b6fbaae..677331f 100644 --- a/lib/features/tournament/presentation/screens/home_screen.dart +++ b/lib/features/tournament/presentation/screens/home_screen.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:schleifchenturnier/features/tournament/presentation/screens/fields_screen.dart'; -import 'package:schleifchenturnier/features/tournament/presentation/screens/stats_screen.dart'; -import 'package:schleifchenturnier/features/tournament/presentation/screens/timer_setup_screen.dart'; -import 'player_management_screen.dart'; // wird gleich erstellt -// Später: timer_screen.dart, fields_screen.dart, stats_screen.dart +import 'player_management_screen.dart'; +import 'timer_setup_screen.dart'; +import 'fields_screen.dart'; +import 'stats_screen.dart'; +import 'settings_screen.dart'; class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({super.key}); @@ -20,7 +20,7 @@ class _HomeScreenState extends ConsumerState { PlayerManagementScreen(), TimerSetupScreen(), FieldsScreen(), - StatsScreen() + StatsScreen(), ]; void _onTabTapped(int index) { @@ -29,6 +29,11 @@ class _HomeScreenState extends ConsumerState { }); } + // Zeigt das Zahnrad nur auf Spieler- und Statistik-Tab + bool _showSettingsIcon() { + return _selectedIndex == 0 || _selectedIndex == 3; + } + @override Widget build(BuildContext context) { return Scaffold( @@ -36,12 +41,28 @@ class _HomeScreenState extends ConsumerState { title: const Text('Schleifchenturnier'), centerTitle: true, backgroundColor: Theme.of(context).colorScheme.primaryContainer, + foregroundColor: Theme.of(context).colorScheme.onPrimaryContainer, + elevation: 4, + actions: _showSettingsIcon() + ? [ + IconButton( + icon: const Icon(Icons.settings), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (_) => const SettingsScreen()), + ); + }, + ), + ] + : null, ), body: _screens[_selectedIndex], bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, currentIndex: _selectedIndex, onTap: _onTabTapped, + selectedItemColor: Theme.of(context).colorScheme.primary, + unselectedItemColor: Colors.grey.shade600, items: const [ BottomNavigationBarItem(icon: Icon(Icons.people), label: 'Spieler'), BottomNavigationBarItem(icon: Icon(Icons.timer), label: 'Timer'), diff --git a/lib/features/tournament/presentation/screens/settings_screen.dart b/lib/features/tournament/presentation/screens/settings_screen.dart new file mode 100644 index 0000000..524f229 --- /dev/null +++ b/lib/features/tournament/presentation/screens/settings_screen.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:schleifchenturnier/features/tournament/presentation/screens/shuffle_settings_screen.dart'; +import '../provider/player_provider.dart'; + +class SettingsScreen extends ConsumerWidget { + const SettingsScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: const Text('Einstellungen'), + centerTitle: true, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + const SizedBox(height: 40), + + // Zurücksetzen der Siege + Card( + child: ListTile( + leading: const Icon(Icons.restore, color: Colors.orange, size: 32), + title: const Text( + 'Alle Siege zurücksetzen', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + subtitle: const Text('Setzt die Schleifchen-Anzahl aller Spieler auf 0'), + trailing: const Icon(Icons.arrow_forward_ios), + onTap: () { + _showResetConfirmationDialog(context, ref); + }, + ), + ), + Card( + child: ListTile( + leading: const Icon(Icons.shuffle, color: Colors.deepPurple, size: 32), + title: const Text('Shuffle-Einstellungen', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + subtitle: const Text('Modus und Balance-Optionen konfigurieren'), + trailing: const Icon(Icons.arrow_forward_ios), + onTap: () { + Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ShuffleSettingsScreen())); + }, + ), + ), + + const Spacer(), + + // Version oder Footer + const Text( + 'Schleifchenturnier v1.0\nMade with ❤️ für die beste Volleyball-Crew', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey, fontSize: 14), + ), + const SizedBox(height: 20), + ], + ), + ), + ); + } + + void _showResetConfirmationDialog(BuildContext context, WidgetRef ref) { + showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('Wirklich alle Siege zurücksetzen?'), + content: const Text( + 'Dadurch werden alle Schleifchen (Siege) aller Spieler auf 0 gesetzt.\n\nDas kann nicht rückgängig gemacht werden!', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(ctx), + child: const Text('Abbrechen'), + ), + ElevatedButton( + style: ElevatedButton.styleFrom(backgroundColor: Colors.red), + onPressed: () { + ref.read(playerListProvider.notifier).resetAllWins(); + Navigator.pop(ctx); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Alle Siege wurden zurückgesetzt!'), + backgroundColor: Colors.orange, + ), + ); + }, + child: const Text('Zurücksetzen'), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/tournament/presentation/screens/shuffle_settings_screen.dart b/lib/features/tournament/presentation/screens/shuffle_settings_screen.dart new file mode 100644 index 0000000..7e1473a --- /dev/null +++ b/lib/features/tournament/presentation/screens/shuffle_settings_screen.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../provider/shuffle_settings_provider.dart'; + +class ShuffleSettingsScreen extends ConsumerWidget { + const ShuffleSettingsScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final settings = ref.watch(shuffleSettingsProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('Shuffle-Einstellungen'), + centerTitle: true, + ), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + const Text( + 'Shuffle-Modus', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + RadioListTile( + title: const Text('Random'), + subtitle: const Text('Einfach zufällig mischen'), + value: ShuffleMode.random, + groupValue: settings.mode, + onChanged: (val) => ref.read(shuffleSettingsProvider.notifier).update(mode: val), + ), + RadioListTile( + title: const Text('Ausgewogen'), + subtitle: const Text('Gute Balance von Geschlecht und Level'), + value: ShuffleMode.balanced, + groupValue: settings.mode, + onChanged: (val) => ref.read(shuffleSettingsProvider.notifier).update(mode: val), + ), + RadioListTile( + title: const Text('Aggressiv ausgewogen'), + subtitle: const Text('Strenge Regeln – perfekte Teams oder gar nicht'), + value: ShuffleMode.aggressive, + groupValue: settings.mode, + onChanged: (val) => ref.read(shuffleSettingsProvider.notifier).update(mode: val), + ), + RadioListTile( + title: const Text('Positionsbasiert'), + subtitle: const Text('Versucht ideale Aufstellung: 1 Zuspieler, 2 Außen, etc.'), + value: ShuffleMode.positionBased, + groupValue: settings.mode, + onChanged: (val) => ref.read(shuffleSettingsProvider.notifier).update(mode: val), + ), + + const Divider(height: 40), + + const Text( + 'Zusätzliche Optionen', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + SwitchListTile( + title: const Text('Geschlechterbalance priorisieren'), + subtitle: const Text('Versucht 3M/3F pro Team'), + value: settings.prioritizeGenderBalance, + onChanged: (val) => ref.read(shuffleSettingsProvider.notifier).update(prioritizeGenderBalance: val), + ), + SwitchListTile( + title: const Text('Level-Balance priorisieren'), + subtitle: const Text('Ähnliche Spielstärke pro Team'), + value: settings.prioritizeLevelBalance, + onChanged: (val) => ref.read(shuffleSettingsProvider.notifier).update(prioritizeLevelBalance: val), + ), + SwitchListTile( + title: const Text('Positionsbesetzung beachten'), + subtitle: const Text('Ideale Aufstellung pro Team versuchen'), + value: settings.usePositions, + onChanged: (val) => ref.read(shuffleSettingsProvider.notifier).update(usePositions: val), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index d4b4396..e939dee 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:schleifchenturnier/features/tournament/data/models/player_model.dart'; +import 'package:schleifchenturnier/features/tournament/presentation/provider/shuffle_settings_provider.dart'; import 'features/tournament/presentation/screens/home_screen.dart'; @@ -14,6 +15,8 @@ void main() async{ await Hive.initFlutter(); Hive.registerAdapter(PlayerModelAdapter()); + Hive.registerAdapter(ShuffleSettingsAdapter()); + Hive.registerAdapter(ShuffleModeAdapter()); Hive.registerAdapter(GenderAdapter()); Hive.registerAdapter(SkillLevelAdapter()); Hive.registerAdapter(PositionAdapter());