Merge branch 'development' into feature/168-teamspiele-implementieren

# Conflicts:
#	lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart
#	lib/presentation/views/main_menu/match_view/match_detail_view.dart
#	lib/presentation/views/main_menu/match_view/match_result_view.dart
#	lib/presentation/widgets/buttons/main_menu_button.dart
#	pubspec.yaml
This commit is contained in:
2026-05-18 01:06:46 +02:00
39 changed files with 846 additions and 380 deletions

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tallee/core/custom_theme.dart';
import 'package:tallee/core/enums.dart';
@@ -48,7 +49,12 @@ class CustomWidthButton extends StatelessWidget {
)!;
return ElevatedButton(
onPressed: onPressed,
onPressed: onPressed == null
? null
: () async {
await HapticFeedback.selectionClick();
onPressed!.call();
},
style: ElevatedButton.styleFrom(
foregroundColor: textcolor,
disabledForegroundColor: disabledTextColor,
@@ -78,7 +84,12 @@ class CustomWidthButton extends StatelessWidget {
: Color.lerp(CustomTheme.primaryColor, Colors.black, 0.5)!;
return OutlinedButton(
onPressed: onPressed,
onPressed: onPressed == null
? null
: () async {
await HapticFeedback.selectionClick();
onPressed!.call();
},
style: OutlinedButton.styleFrom(
foregroundColor: textcolor,
disabledForegroundColor: disabledTextColor,
@@ -110,7 +121,12 @@ class CustomWidthButton extends StatelessWidget {
disabledBackgroundColor = Colors.transparent;
return TextButton(
onPressed: onPressed,
onPressed: onPressed == null
? null
: () async {
await HapticFeedback.selectionClick();
onPressed!.call();
},
style: TextButton.styleFrom(
foregroundColor: textcolor,
disabledForegroundColor: disabledTextColor,

View File

@@ -0,0 +1,23 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
class HapticBackButton extends StatelessWidget {
const HapticBackButton({super.key});
@override
Widget build(BuildContext context) {
final iconData = switch (defaultTargetPlatform) {
TargetPlatform.iOS ||
TargetPlatform.macOS => Icons.arrow_back_ios_new_rounded,
_ => Icons.arrow_back_rounded,
};
return HapticIconButton(
icon: Icon(iconData),
onPressed: () async {
Navigator.of(context).maybePop();
},
);
}
}

View File

@@ -0,0 +1,23 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
class HapticCloseButton extends StatelessWidget {
const HapticCloseButton({super.key});
@override
Widget build(BuildContext context) {
final iconData = switch (defaultTargetPlatform) {
TargetPlatform.iOS || TargetPlatform.macOS => CupertinoIcons.xmark,
_ => Icons.close_rounded,
};
return HapticIconButton(
icon: Icon(iconData),
onPressed: () async {
Navigator.of(context).maybePop();
},
);
}
}

View File

@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class HapticIconButton extends StatelessWidget {
const HapticIconButton({
super.key,
required this.icon,
required this.onPressed,
this.iconSize,
this.color,
this.padding,
this.alignment,
this.constraints,
this.style,
this.isSelected,
this.selectedIcon,
});
final Widget icon;
final VoidCallback? onPressed;
final double? iconSize;
final Color? color;
final EdgeInsetsGeometry? padding;
final AlignmentGeometry? alignment;
final BoxConstraints? constraints;
final ButtonStyle? style;
final bool? isSelected;
final Widget? selectedIcon;
@override
Widget build(BuildContext context) {
return IconButton(
iconSize: iconSize,
highlightColor: Colors.transparent, //disable splash animation
color: color,
padding: padding,
alignment: alignment ?? Alignment.center,
constraints: constraints,
style: style,
isSelected: isSelected,
selectedIcon: selectedIcon,
icon: icon,
onPressed: onPressed == null
? null
: () async {
await HapticFeedback.selectionClick();
onPressed!.call();
},
);
}
}

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class MainMenuButton extends StatefulWidget {
/// A button for the main menu with an optional icon and a press animation.
@@ -84,14 +85,21 @@ class _MainMenuButtonState extends State<MainMenuButton>
} else {
_animationController.forward();
if (widget.onLongPressed != null) {
_longPressTimer = Timer(const Duration(milliseconds: 400), () {
_isLongPressing = true;
widget.onLongPressed?.call();
_repeatTimer = Timer.periodic(
const Duration(milliseconds: 250),
(_) => widget.onLongPressed?.call(),
);
});
_longPressTimer = Timer(
const Duration(milliseconds: 400),
() async {
_isLongPressing = true;
widget.onLongPressed?.call();
await HapticFeedback.heavyImpact();
_repeatTimer = Timer.periodic(
const Duration(milliseconds: 250),
(_) async {
widget.onLongPressed?.call();
await HapticFeedback.heavyImpact();
},
);
},
);
}
}
},
@@ -101,6 +109,7 @@ class _MainMenuButtonState extends State<MainMenuButton>
} else {
_cancelTimers();
if (mounted && !_isLongPressing) {
await HapticFeedback.selectionClick();
widget.onPressed?.call();
}
_isLongPressing = false;

View File

@@ -1,4 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:tallee/core/enums.dart';
import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart';
@@ -26,7 +27,10 @@ class CustomDialogAction extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AnimatedDialogButton(
onPressed: onPressed,
onPressed: () async {
await HapticFeedback.selectionClick();
onPressed.call();
},
buttonText: text,
buttonType: buttonType,
isDescructive: isDestructive,

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:tallee/core/common.dart';
import 'package:tallee/core/constants.dart';
@@ -143,7 +144,8 @@ class _PlayerSelectionState extends State<PlayerSelection> {
text: player.name,
suffixText: getNameCountText(player),
onIconTap: () {
setState(() {
setState(() async {
await HapticFeedback.selectionClick();
// Removes the player from the selection and notifies the parent.
selectedPlayers.remove(player);
widget.onChanged([...selectedPlayers]);
@@ -197,7 +199,8 @@ class _PlayerSelectionState extends State<PlayerSelection> {
text: suggestedPlayers[index].name,
suffixText: getNameCountText(suggestedPlayers[index]),
icon: Icons.add,
onPressed: () {
onPressed: () async {
await HapticFeedback.selectionClick();
setState(() {
// If the player is not already selected
if (!selectedPlayers.contains(
@@ -294,8 +297,10 @@ class _PlayerSelectionState extends State<PlayerSelection> {
if (success) {
_handleSuccessfulPlayerCreation(createdPlayer);
await HapticFeedback.successNotification();
showSnackBarMessage(loc.successfully_added_player(playerName));
} else {
await HapticFeedback.errorNotification();
showSnackBarMessage(loc.could_not_add_player(playerName));
}
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tallee/core/custom_theme.dart';
class ChooseTile extends StatefulWidget {
@@ -30,7 +31,14 @@ class _ChooseTileState extends State<ChooseTile> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onPressed,
onTap: widget.onPressed != null
? () async {
await HapticFeedback.selectionClick();
if (widget.onPressed != null) {
widget.onPressed!.call();
}
}
: null,
child: Container(
margin: CustomTheme.tileMargin,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tallee/core/common.dart';
import 'package:tallee/core/custom_theme.dart';
import 'package:tallee/core/enums.dart';
@@ -53,8 +54,18 @@ class GameTile extends StatelessWidget {
final gameColor = badgeColor ?? getColorFromGameColor(GameColor.orange);
return GestureDetector(
onTap: onTap,
onLongPress: onLongPress,
onTap: () async {
await HapticFeedback.selectionClick();
if (onTap != null) {
onTap!.call();
}
},
onLongPress: () async {
await HapticFeedback.heavyImpact();
if (onLongPress != null) {
onLongPress!.call();
}
},
child: AnimatedContainer(
margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10),
decoration: !isHighlighted

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tallee/core/common.dart';
import 'package:tallee/core/custom_theme.dart';
import 'package:tallee/data/models/group.dart';
@@ -33,7 +34,12 @@ class _GroupTileState extends State<GroupTile> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap,
onTap: () async {
await HapticFeedback.selectionClick();
if (widget.onTap != null) {
widget.onTap!.call();
}
},
child: AnimatedContainer(
margin: CustomTheme.standardMargin,
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tallee/core/custom_theme.dart';
import 'package:tallee/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart';
import 'package:tallee/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart';
@@ -15,8 +16,10 @@ class LicenseTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(
onTap: () async {
final navigator = Navigator.of(context);
await HapticFeedback.selectionClick();
navigator.push(
MaterialPageRoute(
builder: (context) => LicenseDetailView(package: package),
),

View File

@@ -0,0 +1,57 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tallee/core/custom_theme.dart';
class CustomCheckboxListTile extends StatelessWidget {
const CustomCheckboxListTile({
super.key,
required this.text,
required this.value,
required this.onChanged,
});
final String text;
final bool value;
final ValueChanged<bool> onChanged;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () async {
await HapticFeedback.selectionClick();
onChanged(!value);
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: CustomTheme.boxColor,
border: Border.all(color: CustomTheme.boxBorderColor),
borderRadius: CustomTheme.standardBorderRadiusAll,
),
child: Row(
children: [
Checkbox(
value: value,
onChanged: (bool? v) async {
await HapticFeedback.selectionClick();
if (v == null) return;
onChanged(v);
},
),
Expanded(
child: Text(
text,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
);
}
}

View File

@@ -1,6 +1,7 @@
import 'dart:core' hide Match;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:tallee/core/common.dart';
import 'package:tallee/core/custom_theme.dart';
@@ -52,7 +53,10 @@ class _MatchTileState extends State<MatchTile> {
final loc = AppLocalizations.of(context);
return GestureDetector(
onTap: widget.onTap,
onTap: () async {
await HapticFeedback.selectionClick();
widget.onTap.call();
},
child: Container(
margin: EdgeInsets.zero,
width: widget.width,
@@ -332,6 +336,9 @@ class _MatchTileState extends State<MatchTile> {
return '${loc.winner}: $mvpNames (${getPointLabel(loc, mvpScore)})';
} else if (ruleset == Ruleset.placement) {
return '${loc.winner}: ${widget.match.mvp.first.name}';
} else if (ruleset == Ruleset.multipleWinners) {
final mvpNames = widget.match.mvp.map((player) => player.name).join(', ');
return '${loc.winners}: $mvpNames';
}
return '${loc.winner}: n.A.';
}

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tallee/core/custom_theme.dart';
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
@@ -36,7 +37,10 @@ class SettingsListTile extends StatelessWidget {
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.95,
child: GestureDetector(
onTap: onPressed ?? () {},
onTap: () async {
await HapticFeedback.selectionClick();
onPressed?.call();
},
child: Container(
margin: EdgeInsets.zero,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),