feat: add haptic feedback to more user interactions
Some checks failed
Pull Request Pipeline / test (pull_request) Successful in 49s
Pull Request Pipeline / lint (pull_request) Failing after 50s

This commit is contained in:
2026-05-11 10:27:35 +02:00
parent 1d20127af4
commit bc59d1d91c
20 changed files with 165 additions and 53 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

@@ -1,6 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
class HapticBackButton extends StatelessWidget {
const HapticBackButton({super.key});
@@ -13,10 +13,9 @@ class HapticBackButton extends StatelessWidget {
_ => Icons.arrow_back_rounded,
};
return IconButton(
return HapticIconButton(
icon: Icon(iconData),
onPressed: () async {
await HapticFeedback.mediumImpact();
Navigator.of(context).maybePop();
},
);

View File

@@ -1,7 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart';
class HapticCloseButton extends StatelessWidget {
const HapticCloseButton({super.key});
@@ -13,10 +13,9 @@ class HapticCloseButton extends StatelessWidget {
_ => Icons.close_rounded,
};
return IconButton(
return HapticIconButton(
icon: Icon(iconData),
onPressed: () async {
await HapticFeedback.mediumImpact();
Navigator.of(context).maybePop();
},
);

View File

@@ -0,0 +1,54 @@
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.tooltip,
this.iconSize,
this.color,
this.padding,
this.alignment,
this.constraints,
this.style,
this.isSelected,
this.selectedIcon,
});
final Widget icon;
final VoidCallback? onPressed;
final String? tooltip;
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(
tooltip: tooltip,
iconSize: iconSize,
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.
@@ -78,6 +79,7 @@ class _MainMenuButtonState extends State<MainMenuButton>
onTapUp: (_) async {
_cancelTimers();
if (mounted && !_isLongPressing) {
await HapticFeedback.selectionClick();
widget.onPressed();
}
_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

@@ -144,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]);

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,12 @@ class _ChooseTileState extends State<ChooseTile> {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onPressed,
onTap: () async {
await HapticFeedback.vibrate();
if (widget.onPressed != null) {
widget.onPressed!.call();
}
},
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/custom_theme.dart';
class CustomCheckboxListTile extends StatelessWidget {
@@ -16,7 +17,10 @@ class CustomCheckboxListTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => onChanged(!value),
onTap: () async {
await HapticFeedback.selectionClick();
onChanged(!value);
},
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
padding: const EdgeInsets.symmetric(horizontal: 2),
@@ -29,7 +33,8 @@ class CustomCheckboxListTile extends StatelessWidget {
children: [
Checkbox(
value: value,
onChanged: (bool? v) {
onChanged: (bool? v) async {
await HapticFeedback.selectionClick();
if (v == null) return;
onChanged(v);
},

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';
@@ -51,7 +52,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,